def __init__(self, _env, _output=None, _params = {}):    
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.idle_times = []
        self.utilization = []
        self.diagram = """blockdiag {       
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Buffer"];
                       A;
                       } """          
        
        self.params = {}
        
        self.params['specification'] = """
<h3>General description</h3>
A buffer is used to store cassettes temporarily.
The space available in the buffer can be configured.\n
        """
        
        self.params['name'] = ""
        self.params['type'] = "Buffer"        
        self.params['max_cassette_no'] = 50
        self.params['max_cassette_no_desc'] = "Number of cassette positions available"
        self.params['max_cassette_no_type'] = "configuration"
        self.params.update(_params)

        # stores cassette number references
        self.input = CassetteContainer(self.env,"input",self.params['max_cassette_no'],True)
        self.output = self.input
Exemple #2
0
    def __init__(self, _env, _output=None, _params = {}):
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.idle_times = []
        self.utilization = []
        self.diagram = """blockdiag {       
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Input"];
                       B [label = "Bin"];
                       A -> B;                    
                       } """      
        
        self.params = {}

        self.params['specification'] = """
<h3>General description</h3>
WaferBin is an imaginary machine that accepts wafer cassettes and places them in an infinitely sized container.
The user can set a time period between attempts to place the cassettes into the container.\n
\n
<h3>Description of the algorithm</h3>
There is one simple loop that consists of two steps:
<ol>
<li>Check if the number of wafers at the input is at least that of a cassette. If so, place one cassette in the container.</li>
<li>Wait for a defined period of time</li>
</ol>
\n
        """         
        
        self.params['name'] = ""
        self.params['type'] = "WaferBin"        
        self.params['max_batch_no'] = 8
        self.params['max_batch_no_desc'] = "Number of input cassette positions"
        self.params['max_batch_no_type'] = "configuration"
        self.params['wait_time'] = 10
        self.params['wait_time_desc'] = "Wait period between wafer removal attempts (seconds)"
        self.params['wait_time_type'] = "automation"
                   
        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(round(self.env.now,1)) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)
            
#        string = str(self.env.now) + " - [" + self.params['type'] + "][" + self.params['name'] + "] Added a wafer bin" #DEBUG
#        self.output_text.sig.emit(string) #DEBUG
      
        self.input = CassetteContainer(self.env,"input",self.params['max_batch_no'])
        self.output = InfiniteContainer(self.env,"output")

        self.maintenance_needed = False
        
        self.env.process(self.run())
Exemple #3
0
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.diagram = """blockdiag {     
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       default_group_color = none               
                       A [label = "Input"];
                       B [label = "Load station"];
                       C [label = "Tube PECVD", stacked];
                       D [label = "Cooldown shelves", stacked];
                       E [label = "Output"];
                       A -> B -> C -> D -> B;
                       C -> D [folded];
                       B -> E [folded];
                       group { B; E; }                    
                       group { C; D; }
                       } """

        self.params = {}
        self.params['specification'] = """
<h3>General description</h3>
A TubePECVD serves to deposit dielectric layers on wafers.
Wafers are first loaded into boats in the loadstation and then transferred to the process tubes where the deposition are performed.
When the processes are finished the boats are transferred to a cooldown shelf and then back to the loadstation for wafer load-out.
There is a downtime procedure in light of the required boat cleaning after using it for a defined number of depositions.
The cleaning itself is done externally but the boats need to undergo a coating run before re-using them.
There is also a downtime procedure available for preventive maintenance after a set number of process runs, but this is not enabled by default.
<h3>Description of the algorithm</h3>
The main loop is primarily concerned with the boat transport inside the tool, as described in the list below:
<ol>
<li>Go into downtime mode if a set number of processes has been run and all wafers were loaded out</li>
<li>Try to move boat from furnace to cooldown; try full boats first</li>
<li>Try to move boat from cooldown to loadstation; try full boats first</li>
<li>Perform action on boat in loadstation depending on state of boat and wafers:</li>
<ol>
<li>Perform coating run on empty boat if boat has been used for a set number of times</li>
<li>Start wafer load-in if boat is empty and wafers are available, except if downtime is required</li>
<li>If boat is sitting idle and there are batches in the system that need to be loaded out, check idle time and move boat to furnace if it is too long</li>
<li>If boat is full and not yet processed, try to move it to a furnace for processing</li>
</ol></ol>
<p>The wafer loading and unloading actions are separate processes that are triggered by the main loop.
The actions themselves consists of a simple series of load/unload steps.
In each load/unload step the automation loadsize is transferred into or out of the boat in the loadstation with a set delay.
The process batch size therefore needs to be a multiple of the automation loadsize.</p>
        """

        self.params['name'] = ""
        self.params['type'] = "TubePECVD"
        self.params['batch_size'] = 4
        self.params[
            'batch_size_desc'] = "Number of cassettes in a single process batch"
        self.params['batch_size_type'] = "configuration"
        self.params['process_time'] = 30
        self.params[
            'process_time_desc'] = "Time for a single process (minutes)"
        self.params['process_time_type'] = "process"
        self.params['cool_time'] = 10
        self.params['cool_time_desc'] = "Time for a single cooldown (minutes)"
        self.params['cool_time_type'] = "process"

        self.params['runs_before_boatclean'] = 100
        self.params[
            'runs_before_boatclean_desc'] = "Number of PECVD process runs before boat needs to be cleaned (0 to disable function)"
        self.params['runs_before_boatclean_type'] = "downtime"
        self.params['coating_run_duration'] = 75
        self.params[
            'coating_run_duration_desc'] = "Time for a single PECVD coating run (minutes)"
        self.params['coating_run_duration_type'] = "downtime"

        self.params['downtime_runs'] = 0
        self.params[
            'downtime_runs_desc'] = "Number of PECVD process runs before downtime of the whole tool (0 to disable function)"
        self.params['downtime_runs_type'] = "downtime"
        self.params['downtime_duration'] = 60
        self.params[
            'downtime_duration_desc'] = "Time for a single tool downtime cycle (minutes)"
        self.params['downtime_duration_type'] = "downtime"

        self.params['no_of_processes'] = 5
        self.params[
            'no_of_processes_desc'] = "Number of process locations in the tool"
        self.params['no_of_processes_type'] = "configuration"
        self.params['no_of_cooldowns'] = 4
        self.params[
            'no_of_cooldowns_desc'] = "Number of cooldown locations in the tool"
        self.params['no_of_cooldowns_type'] = "configuration"

        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of cassette positions at input and the same number at output"
        self.params['max_cassette_no_type'] = "configuration"

        self.params['no_of_boats'] = 7
        self.params['no_of_boats_desc'] = "Number of boats available"
        self.params['no_of_boats_type'] = "configuration"

        self.params['transfer0_time'] = 60
        self.params[
            'transfer0_time_desc'] = "Time for boat transfer from load-in to process tube (seconds)"
        self.params['transfer0_time_type'] = "automation"
        self.params['transfer1_time'] = 60
        self.params[
            'transfer1_time_desc'] = "Time for boat transfer from process tube to cooldown (seconds)"
        self.params['transfer1_time_type'] = "automation"
        self.params['transfer2_time'] = 60
        self.params[
            'transfer2_time_desc'] = "Time for boat transfer from cooldown to load-out (seconds)"
        self.params['transfer2_time_type'] = "automation"

        self.params['automation_loadsize'] = 25
        self.params[
            'automation_loadsize_desc'] = "Number of units per loading/unloading automation cycle"
        self.params['automation_loadsize_type'] = "automation"
        self.params['automation_time'] = 10
        self.params[
            'automation_time_desc'] = "Time for a single loading/unloading automation cycle (seconds)"
        self.params['automation_time_type'] = "automation"

        self.params['idle_boat_timeout'] = 300
        self.params[
            'idle_boat_timeout_desc'] = "Time before idle boat in the loadstation is moved to furnace to enable continued load-out (seconds)"
        self.params['idle_boat_timeout_type'] = "automation"

        self.params['wait_time'] = 10
        self.params[
            'wait_time_desc'] = "Wait period between boat transport attempts (seconds)"
        self.params['wait_time_type'] = "automation"

        self.params['loop_begin'] = False
        self.params['loop_begin_type'] = "immutable"
        self.params['loop_end'] = False
        self.params['loop_end_type'] = "immutable"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params['mttr_desc'] = "Mean time to repair (minutes)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        string = ""

        if self.output_text and self.params['cassette_size'] == -1:
            string += str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information. "

        if self.params['cassette_size'] == -1:
            self.params['cassette_size'] = 100

        self.loop_begin = self.params['loop_begin']
        self.loop_end = self.params['loop_end']

        if not self.loop_begin == self.loop_end:
            string += "[" + self.params['name'] + "][" + self.params[
                'name'] + "] "
            string += "WARNING: Cassette loop definition is not consistent for in- and output. "

        if self.params['max_cassette_no'] < self.params['batch_size']:
            string += "[" + self.params['type'] + "][" + self.params[
                'name'] + "] "
            string += "WARNING: Input buffer is smaller than batch-size. Tool will not start. "

        if (not (self.output_text
                 == None)) and (self.params['cassette_size'] %
                                self.params['automation_loadsize']):
            string += "[" + self.params['name'] + "][" + self.params[
                'name'] + "] "
            string += "WARNING: Automation loadsize is not a multiple of cassette size. Automation will not work. "

        if len(string):
            self.output_text.sig.emit(string)

        self.transport_counter = 0
        self.batches_loaded = 0
        self.load_in_start = self.env.event()
        self.load_out_start = self.env.event()
        self.process_counter = 0

        ### Add input and boat load/unload location ###
        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'],
                                       not self.loop_end)

        if not self.loop_end:
            buffer_size = self.params['batch_size'] * (
                self.params['no_of_processes'] +
                self.params['no_of_cooldowns'])
            self.empty_cassette_buffer = CassetteContainer(
                self.env, "buffer", buffer_size, True)

        ### Add boats ###
        self.boat = []  # container for keeping tracking of wafer count
        self.boat_runs = []  # keep track of number of runs
        self.boat_status = [
        ]  # 0 is unprocessed; 1 is processed; 2 is cooled down
        for i in range(self.params['no_of_boats']):
            self.boat.append(
                BatchContainer(
                    self.env, "boat",
                    self.params['batch_size'] * self.params['cassette_size'],
                    1))
            self.boat_runs.append(0)
            self.boat_status.append(0)

        ### Add furnaces ###
        self.furnace = []
        self.furnace_status = []
        self.furnace_runs = []
        for i in range(self.params['no_of_processes']):
            self.furnace.append(
                -1)  # -1 is empty; 0 and onwards is boat number
            self.furnace_status.append(0)  # 0 is free; 1 is busy
            self.furnace_runs.append(
                0)  # keep track of the number of runs performed in the furnace

        ### Add cooldowns ###
        self.cooldown = []
        self.cooldown_status = []
        for i in range(self.params['no_of_cooldowns']):
            self.cooldown.append(
                -1)  # -1 is empty; 0 and onwards is boat number
            self.cooldown_status.append(0)  # 0 is free; 1 is busy

        ### Add loadstation ###
        self.loadstation = -1  # -1 is empty; 0 and onwards is boat number
        self.loadstation_status = 0  # 0 is free; 1 is busy

        ### Add output ###
        self.output = CassetteContainer(self.env, "output",
                                        self.params['max_cassette_no'],
                                        not self.loop_begin)

        ### Distribute boats ###
        no_boats = self.params['no_of_boats']

        if no_boats:
            self.loadstation = 0
            no_boats -= 1

        if no_boats:
            for i in range(self.params['no_of_cooldowns']):
                self.cooldown[i] = i + 1
                no_boats -= 1

                if not no_boats:
                    break

        if no_boats:
            for i in range(self.params['no_of_processes']):
                self.furnace[i] = i + self.params['no_of_cooldowns'] + 1
                no_boats -= 1

                if not no_boats:
                    break

        self.downtime_finished = None
        self.technician_resource = simpy.Resource(self.env, 1)
        self.downtime_duration = 60 * self.params['downtime_duration']
        self.maintenance_needed = False

        random.seed(self.params['random_seed'])

        self.mtbf_enable = False
        if (self.params['mtbf'] > 0) and (self.params['mttr'] > 0):
            self.next_failure = random.expovariate(
                1 / (3600 * self.params['mtbf']))
            self.mtbf_enable = True

        self.env.process(self.run_load_in())
        self.env.process(self.run_load_out())
        self.env.process(self.run_transport(
        ))  # important not to trigger signal before starting load processes
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)

        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.diagram = """blockdiag {      
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';
                       default_group_color = none               
                       A [label = "Input"];
                       B [label = "Output"];               
                       C [label = "Loadlock0"];
                       D [label = "Loadlock1"];
                       E [label = "Process belt0"];                       
                       F [label = "Process belt1"];
                       G [label = "Buffer0"];
                       H [label = "Buffer1"];
                       A -> C;
                       B <- D;
                       C <-> E;
                       E <-> G;
                       F <-> H;
                       group { A; B; }
                       group { C; D; color = "#CCCCCC"}
                       group { E; F; G; H; color = "#CCCCCC"}
                       
                       } """

        self.params = {}

        self.params['specification'] = """
<h3>General description</h3>
An ion implanter is used for applying dopant into wafers using an ion beam.
Cassettes are loaded into the loadlocks, which are then held for a set time for evacuation.
Subsequently, the wafers are transported on belts to be exposed to beam and then enter into buffer cassettes.
When the buffer cassette is full, the wafers return on the same belt to the loadlock.
After repressurization the cassettes are placed in the output buffer.
There is a downtime procedure available during to simulate a maintenance procedure.
During this time the wafer load-in is paused.\n
        """

        self.params['name'] = ""
        self.params['type'] = "IonImplanter"
        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of cassette positions at input and the same number at output"
        self.params['max_cassette_no_type'] = "configuration"

        self.params['transfer0_time'] = 30
        self.params[
            'transfer0_time_desc'] = "Time for single batch transfer from input to loadlock (seconds)"
        self.params['transfer0_time_type'] = "automation"
        self.params['transfer1_time'] = 30
        self.params[
            'transfer1_time_desc'] = "Time for single batch transfer from loadlock to output (seconds)"
        self.params['transfer1_time_type'] = "automation"

        self.params['evacuation_time'] = 240
        self.params[
            'evacuation_time_desc'] = "Time for single loadlock evacuation (seconds)"
        self.params['evacuation_time_type'] = "process"
        self.params['repressurization_time'] = 90
        self.params[
            'repressurization_time_desc'] = "Time for single loadlock repressurization (seconds)"
        self.params['repressurization_time_type'] = "process"

        self.params['implant_belt_speed'] = 10.0
        self.params[
            'implant_belt_speed_desc'] = "Speed at which all units travel (meters per minute)"
        self.params['implant_belt_speed_type'] = "process"
        self.params['implant_belt_length'] = 2.0
        self.params[
            'implant_belt_length_desc'] = "Distance between loadlock and buffer cassette (meters)"
        self.params['implant_belt_length_type'] = "configuration"
        self.params['unit_distance'] = 0.2
        self.params[
            'unit_distance_desc'] = "Minimal distance between wafers (meters)"
        self.params['unit_distance_type'] = "configuration"

        self.params['downtime_interval'] = 100
        self.params[
            'downtime_interval_desc'] = "Interval between downtime cycles (hours)"
        self.params['downtime_interval_type'] = "downtime"
        self.params['downtime_duration'] = 60
        self.params[
            'downtime_duration_desc'] = "Time for a single tool downtime cycle (minutes)"
        self.params['downtime_duration_type'] = "downtime"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params[
            'mttr_desc'] = "Mean time to repair (minutes) (0 to disable function)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['batch_size'] = 2
        self.params['batch_size_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)

        if self.params['cassette_size'] == -1:
            self.params['cassette_size'] = 100

        ### Input buffer ###
        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'], True)

        ### Implant lanes ###
        self.implant_lanes_resource = simpy.Resource(self.env, 1)

        ## Load locks ##
        self.batchprocesses = []
        for i in range(0, 2):
            process_params = self.params.copy()
            process_params['name'] = "ll" + str(i)
            self.batchprocesses.append(
                loadlock(self.env, self.output_text, process_params,
                         self.implant_lanes_resource))

        ### Output buffer ###
        self.output = CassetteContainer(self.env, "output",
                                        self.params['max_cassette_no'], True)

        ### Load-in transport ###
        batchconnections = []

        for i in range(0, 2):
            batchconnections.append([
                self.input, self.batchprocesses[i],
                self.params['transfer0_time']
            ])

        transport_params = self.params.copy()
        transport_params['name'] = "ii0"
        transport_params['mtbf'] = self.params['mtbf']
        transport_params['mttr'] = self.params['mttr']
        transport_params['random_seed'] = self.params['random_seed']
        self.downtime_finished = None
        self.technician_resource = None
        self.downtime_duration = 0
        self.maintenance_needed = False
        self.transport0 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params,
                                         self)

        ### Load-out transport ###
        # separate transporter so we can easily count processed wafers
        batchconnections = []

        for i in range(0, 2):
            batchconnections.append([
                self.batchprocesses[i], self.output,
                self.params['transfer1_time']
            ])

        transport_params = self.params.copy()
        transport_params['name'] = "ii1"
        self.transport1 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params)
Exemple #5
0
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.diagram = """blockdiag {    
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Input"];
                       B [label = "Process lanes", stacked];
                       C [label = "Output"];
                       A -> B -> C;                        
                       } """

        self.params = {}

        self.params['specification'] = """
<h3>General description</h3>
A single side etch is used for etching, polishing or texturing wafers.
Each lane runs independently and continuously, but can only accept a new unit after a certain time interval to avoid wafer collisions.
Each lane is fed separately with new wafers with no interruption for exchanging cassettes.
There is a downtime procedure available where the whole tool goes down for a certain period after running a set number of wafers.
Such downtimes are required for exchanging the etching solution.\n
<h3>Description of the algorithm</h3>
There are three types of program loops to run the tool:
<ol>
<li>An independent loop for wafer load-in to each lane.
There is currently no delay for exchanging empty cassettes for full ones.</li>
<li>Wafer transport consists of single process that progresses wafers along all lanes with fixed time increments.
The time increment is determined by the belt speed and unit distance.</li>
<li>Wafer load-out is done with the same time increment and for all lanes at the same time.</li>
</ol>
<p>The downtime procedure pauses the wafer load-in process for a set duration.</p>
\n
        """

        self.params['name'] = ""
        self.params['type'] = "SingleSideEtch"
        self.params['no_of_lanes'] = 5
        self.params['no_of_lanes_desc'] = "Number of process lanes"
        self.params['no_of_lanes_type'] = "configuration"
        self.params['tool_length'] = 8
        self.params[
            'tool_length_desc'] = "Travel distance for wafers between input and output (meters)"
        self.params['tool_length_type'] = "configuration"
        self.params['belt_speed'] = 1.8
        self.params[
            'belt_speed_desc'] = "Speed at which all units travel (meters per minute)"
        self.params['belt_speed_type'] = "process"
        self.params['unit_distance'] = 0.2
        self.params[
            'unit_distance_desc'] = "Minimal distance between wafers (meters)"
        self.params['unit_distance_type'] = "configuration"
        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of cassette positions at input and the same number at output"
        self.params['max_cassette_no_type'] = "configuration"

        self.params['downtime_volume'] = 100
        self.params[
            'downtime_volume_desc'] = "Number of entered wafers before downtime (x1000) (0 to disable function)"
        self.params['downtime_volume_type'] = "downtime"
        self.params['downtime_duration'] = 60
        self.params[
            'downtime_duration_desc'] = "Time for a single tool downtime cycle (minutes)"
        self.params['downtime_duration_type'] = "downtime"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params[
            'mttr_desc'] = "Mean time to repair (minutes) (0 to disable function)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)

        if self.params['cassette_size'] == -1:
            self.params['cassette_size'] = 100

        self.transport_counter = 0
        self.process_counter = 0
        self.time_step = 60 * self.params['unit_distance'] / self.params[
            'belt_speed']

        #self.incoming_wafers = 0 # for checking number of incoming wafers

        ### Input ###
        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'])

        ### Array of zeroes represents lanes ###
        self.lanes = []
        for i in range(self.params['no_of_lanes']):
            no_positions = int(self.params['tool_length'] //
                               self.params['unit_distance'])
            self.lanes.append(
                collections.deque([False for rows in range(no_positions)]))

        ### Output ###
        self.output = CassetteContainer(self.env, "output",
                                        self.params['max_cassette_no'])
        self.wafer_out = simpy.Container(self.env)

        self.downtime_finished = None
        self.technician_resource = simpy.Resource(self.env, 1)
        self.downtime_duration = 0
        self.maintenance_needed = False

        random.seed(self.params['random_seed'])

        self.mtbf_enable = False
        if (self.params['mtbf'] > 0) and (self.params['mttr'] > 0):
            self.next_failure = random.expovariate(
                1 / (3600 * self.params['mtbf']))
            self.mtbf_enable = True

        self.env.process(self.run_cassette_load_out())
        self.env.process(self.run_lane_load_out())
        self.env.process(self.run_lanes())
        self.env.process(self.run_cassette_load_in())
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)

        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.diagram = """blockdiag {    
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Input"];
                       B [label = "Oxide etch0", stacked];
                       C [label = "Rinse0", stacked];
                       D [label = "Chemical oxidation", stacked];
                       E [label = "Rinse1", stacked];
                       F [label = "Oxide etch1", stacked];
                       G [label = "Rinse2", stacked];                       
                       H [label = "Dry", stacked];
                       I [label = "Output"];
                       A -> B -> C -> D -> E -> F -> G -> H -> I;
                       D -> E [folded];
                       H -> I [folded];  
                       } """

        self.params = {}

        self.params['specification'] = """
<h3>General description</h3>
<p>A batch clean tool is used for cleaning wafers by creating a chemical oxide and then removing it.
The first step is an HF dip to remove any existing oxide layer and it is followed by chemical oxidation, a second HF dip and a drying step.
The user can configure the tool by setting the number of baths for each process step and for the rinsing steps in between.
It is also possible to configure the transportation time for each of the four transport machines inside the machine.</p>
There are four fixed transport machines inside the machine:
<ul>
<li>Transporter between input, first HF dip and first rinse</li>
<li>Transporter between first rinse, chemical oxidation and second rinse</li>
<li>Transporter between second rinse, second HF dip, third rinse and dryers</li>
<li>Transporter between dryers and output</li>
</ul>
There is currently no downtime procedure available for this tool.
\n
<h3>Description of the algorithm</h3>
The programming code that is specific to the cleaning machine only defines the process baths and the transport connections between them.
The actual processes and transport actions are then performed by generic algorithms.
The main functions inside the process algorithm are:
<ul>
<li>Simulate processes by holding the wafers for a set amount of time.
It places a resource lock onto itself during that time, to prevent any transporter from accessing the process bath.</li>
<li>Checking for the need for downtime procedures</li>
<li>Starting downtime procedures including a resource lock onto itself</li>
</ul>
All these functions in the process algorithm are triggered by the transport algorithm.
This algorithm consists of a single loop that looks at the list of tool connections and checks if any of three actions are possible.
The three possible actions depend on the type of toolconnection:
<ol>
<li>Toolconnection is from container to process chamber: Try to perform transport and start process</li>
<li>Toolconnection is from process chamber to container: Try to perform transport and check if chamber requires downtime</li>
<li>Toolconnection is process chamber to process chamber: Try to perform transport, check downtime and start new process</li>
</ol>
All transport actions include a set delay to simulate the time needed for the transport.
If any action was possible while going over toolconnections list then the loop will restart with going over the list to search for possible actions.
If no action was possible it will wait for a set amount of time (60 seconds by default) before trying again.
\n
        """

        self.params['name'] = ""
        self.params['type'] = "BatchClean"
        self.params['batch_size'] = 4
        self.params[
            'batch_size_desc'] = "Number of cassettes in a single process batch"
        self.params['batch_size_type'] = "configuration"
        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of cassette positions at input and the same number at output"
        self.params['max_cassette_no_type'] = "configuration"

        self.params['oxetch0_baths'] = 1
        self.params[
            'oxetch0_baths_desc'] = "Number of baths for first oxide etch"
        self.params['oxetch0_baths_type'] = "configuration"
        self.params['oxetch0_time'] = 2
        self.params[
            'oxetch0_time_desc'] = "Time for a single oxide etch process in the first bath (minutes)"
        self.params['oxetch0_time_type'] = "process"

        self.params['rinse0_baths'] = 1
        self.params[
            'rinse0_baths_desc'] = "Number of rinse baths after first oxide etch"
        self.params['rinse0_baths_type'] = "configuration"
        self.params['rinse0_time'] = 5
        self.params[
            'rinse0_time_desc'] = "Time for a single rinse cycle after first oxide etch (minutes)"
        self.params['rinse0_time_type'] = "process"

        self.params['chemox_baths'] = 1
        self.params[
            'chemox_baths_desc'] = "Number of baths for chemical oxidation"
        self.params['chemox_baths_type'] = "configuration"
        self.params['chemox_time'] = 5
        self.params[
            'chemox_time_desc'] = "Time for a single chemical oxidation process (minutes)"
        self.params['chemox_time_type'] = "process"

        self.params['rinse1_baths'] = 1
        self.params[
            'rinse1_baths_desc'] = "Number of rinse baths after chemical oxidation"
        self.params['rinse1_baths_type'] = "configuration"
        self.params['rinse1_time'] = 5
        self.params[
            'rinse1_time_desc'] = "Time for a single rinse cycle after chemical oxidation (minutes)"
        self.params['rinse1_time_type'] = "process"

        self.params['oxetch1_baths'] = 1
        self.params[
            'oxetch1_baths_desc'] = "Number of baths for second oxide etch"
        self.params['oxetch1_baths_type'] = "configuration"
        self.params['oxetch1_time'] = 2
        self.params[
            'oxetch1_time_desc'] = "Time for a single oxide etch process in the second bath (minutes)"
        self.params['oxetch1_time_type'] = "process"

        self.params['rinse2_baths'] = 1
        self.params[
            'rinse2_baths_desc'] = "Number of rinse baths after second oxide etch"
        self.params['rinse2_baths_type'] = "configuration"
        self.params['rinse2_time'] = 5
        self.params[
            'rinse2_time_desc'] = "Time for a single rinse cycle after second oxide etch (minutes)"
        self.params['rinse2_time_type'] = "process"

        self.params['dryer_count'] = 2
        self.params['dryer_count_desc'] = "Number of dryers"
        self.params['dryer_count_type'] = "configuration"
        self.params['dry_time'] = 10
        self.params['dry_time_desc'] = "Time for a single dry cycle (minutes)"
        self.params['dry_time_type'] = "process"

        self.params['transfer0_time'] = 60
        self.params[
            'transfer0_time_desc'] = "Time for single transfer by transporter 0 (seconds)"
        self.params['transfer0_time_type'] = "automation"

        self.params['transfer1_time'] = 60
        self.params[
            'transfer1_time_desc'] = "Time for single transfer by transporter 1 (seconds)"
        self.params['transfer1_time_type'] = "automation"

        self.params['transfer2_time'] = 60
        self.params[
            'transfer2_time_desc'] = "Time for single transfer by transporter 2 (seconds)"
        self.params['transfer2_time_type'] = "automation"

        self.params['transfer3_time'] = 60
        self.params[
            'transfer3_time_desc'] = "Time for single transfer by transporter 3 (seconds)"
        self.params['transfer3_time_type'] = "automation"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params[
            'mttr_desc'] = "Mean time to repair (minutes) (0 to disable function)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)

        if self.params['cassette_size'] == -1:
            self.params['cassette_size'] = 100

        ### Add input ###
        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'], True)

        ### Create and add all batchprocesses to list and keep track of positions in list###
        self.batchprocesses = []
        for i in range(0, self.params['oxetch0_baths']):
            process_params = {}
            process_params['name'] = "HF " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['oxetch0_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_rinse0 = self.params['oxetch0_baths']
        for i in range(0, self.params['rinse0_baths']):
            process_params = {}
            process_params['name'] = "Rinse " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['rinse0_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_chemox = first_rinse0 + self.params['rinse0_baths']
        for i in range(0, self.params['chemox_baths']):
            process_params = {}
            process_params['name'] = "Oxid " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['chemox_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_rinse1 = first_chemox + self.params['chemox_baths']
        for i in range(0, self.params['rinse1_baths']):
            process_params = {}
            process_params['name'] = "Rinse " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['rinse1_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_oxetch1 = first_rinse1 + self.params['rinse1_baths']
        for i in range(0, self.params['oxetch1_baths']):
            process_params = {}
            process_params['name'] = "HF " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['oxetch1_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_rinse2 = first_oxetch1 + self.params['oxetch1_baths']
        for i in range(0, self.params['rinse2_baths']):
            process_params = {}
            process_params['name'] = "Rinse " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['rinse2_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        first_dryer = first_rinse2 + self.params['rinse2_baths']
        for i in range(0, self.params['dryer_count']):
            process_params = {}
            process_params['name'] = "Dry " + str(i)
            process_params['batch_size'] = self.params['batch_size']
            process_params['process_time'] = 60 * self.params['dry_time']
            self.batchprocesses.append(
                BatchProcess(self.env, self.output_text, process_params))

        ### Add output ###
        self.output = CassetteContainer(self.env, "output",
                                        self.params['max_cassette_no'], True)

        ### Batch transporter between input, first oxide etch and first rinse ###
        # First check whether batch can be brought to rinse/output, because that has priority
        batchconnections = []

        for i in range(0, self.params['oxetch0_baths']):
            for j in range(first_rinse0,
                           first_rinse0 + self.params['rinse0_baths']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer0_time']
                ])

        for i in range(0, self.params['oxetch0_baths']):
            batchconnections.append([
                self.input, self.batchprocesses[i],
                self.params['transfer0_time']
            ])

        transport_params = {}
        transport_params['name'] = "cl0"
        transport_params['batch_size'] = self.params['batch_size']
        transport_params['cassette_size'] = self.params['cassette_size']
        transport_params['mtbf'] = self.params['mtbf']
        transport_params['mttr'] = self.params['mttr']
        transport_params['random_seed'] = self.params['random_seed']
        self.downtime_finished = None
        self.technician_resource = None
        self.downtime_duration = 0
        self.maintenance_needed = False
        self.transport0 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params,
                                         self)

        ### Batch transporter between first rinse, chemical oxidation and second rinse ###
        # First check whether batch can be brought to rinse/output, because that has priority
        batchconnections = []

        for i in range(first_chemox,
                       first_chemox + self.params['chemox_baths']):
            for j in range(first_rinse1,
                           first_rinse1 + self.params['rinse1_baths']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer1_time']
                ])

        for i in range(first_rinse0,
                       first_rinse0 + self.params['rinse0_baths']):
            for j in range(first_chemox,
                           first_chemox + self.params['chemox_baths']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer1_time']
                ])

        transport_params = {}
        transport_params['name'] = "cl1"
        transport_params['batch_size'] = self.params['batch_size']
        transport_params['cassette_size'] = self.params['cassette_size']
        self.transport1 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params)

        ### Batch transporter between second rinse, second oxide etch, third rinse and dryers ###
        # First check whether batch can be brought to rinse/output, because that has priority
        batchconnections = []

        for i in range(first_rinse2,
                       first_rinse2 + self.params['rinse2_baths']):
            for j in range(first_dryer,
                           first_dryer + self.params['dryer_count']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer2_time']
                ])

        for i in range(first_oxetch1,
                       first_oxetch1 + self.params['oxetch1_baths']):
            for j in range(first_rinse2,
                           first_rinse2 + self.params['rinse2_baths']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer2_time']
                ])

        for i in range(first_rinse1,
                       first_rinse1 + self.params['rinse1_baths']):
            for j in range(first_oxetch1,
                           first_oxetch1 + self.params['oxetch1_baths']):
                batchconnections.append([
                    self.batchprocesses[i], self.batchprocesses[j],
                    self.params['transfer2_time']
                ])

        transport_params = {}
        transport_params['name'] = "cl2"
        transport_params['batch_size'] = self.params['batch_size']
        transport_params['cassette_size'] = self.params['cassette_size']
        self.transport2 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params)

        ### Batch transporter between dryers and output ###
        # First check whether batch can be brought to output, because that has priority
        batchconnections = []

        for i in range(first_dryer, first_dryer + self.params['dryer_count']):
            batchconnections.append([
                self.batchprocesses[i], self.output,
                self.params['transfer3_time']
            ])

        transport_params = {}
        transport_params['name'] = "cl3"
        transport_params['batch_size'] = self.params['batch_size']
        transport_params['cassette_size'] = self.params['cassette_size']
        self.transport3 = BatchTransport(self.env, batchconnections,
                                         self.output_text, transport_params)
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.next_step = self.env.event()
        self.diagram = """blockdiag {       
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Input"];
                       B [label = "Belt"];
                       C [label = "Output"];
                       A -> B -> C;                    
                       } """

        self.params = {}
        self.params['specification'] = """
<h3>General description</h3>
A WaferStacker is used to transfer wafer from cassettes to stacks.
It accepts a number of cassettes, one of which is placed in an unloading position.
From that position the wafers are placed one by one onto a belt.
The belt then transfers the wafers to a pick-up robot that stacks the wafers at the output.
When a cassette is empty there is a time delay for loading a new cassette.\n
<h3>Description of the algorithm</h3>
There are two loops to simulate the tool, one for the wafer unloading from cassetttes and one running the belt and the wafer stacking.
The loop that performs the wafer unloading consists of the following steps:
<ol>
<li>Pick up a wafer if not already available</li>
<li>If current cassette had been empty pause momentarily to simulate loading a cassette</li>
<li>If the first position on the belt is empty, load the wafer onto it</li>
<li>Wait for a set time period, to simulate the wafer placement action</li>
</ol>
The second loop consists of the following steps:
<ol>
<li>If current wafer stack holder had been full pause momentarily to simulate loading a new stack holder</li>
<li>Try to pick up wafer from end position on belt and perform a delay to simulate the action</li>
<li>If wafer available load it into output</li>
<li>Move belt forward by one position and perform a delay to simulate the belt movement time</li>
</ol>
\n
        """

        self.params['name'] = ""
        self.params['type'] = "WaferStacker"
        self.params['stack_size'] = 500
        self.params['stack_size_desc'] = "Number of wafers in a single stack"
        self.params['stack_size_type'] = "configuration"
        self.params['max_stack_no'] = 4
        self.params[
            'max_stack_no_desc'] = "Maximum number of stacks at the output side"
        self.params['max_stack_no_type'] = "configuration"
        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of input cassette positions"
        self.params['max_cassette_no_type'] = "configuration"
        self.params['units_on_belt'] = 5
        self.params[
            'units_on_belt_desc'] = "Number of wafers that fit on the belt"
        self.params['units_on_belt_type'] = "configuration"

        self.params['time_step'] = 1.0
        self.params[
            'time_step_desc'] = "Time for one wafer to progress one position on belt (seconds) (0.1 sec minimum)"
        self.params['time_step_type'] = "automation"
        self.params['time_new_cassette'] = 10
        self.params[
            'time_new_cassette_desc'] = "Time for putting an empty cassette into a wafer unloading position (seconds)"
        self.params['time_new_cassette_type'] = "automation"
        self.params['time_new_stack'] = 10
        self.params[
            'time_new_stack_desc'] = "Time for putting a new stack into the wafer loading position (seconds)"
        self.params['time_new_stack_type'] = "automation"
        self.params['time_pick_and_place'] = 1.0
        self.params[
            'time_pick_and_place_desc'] = "Time for putting a single wafer on a stack (seconds) (0.1 sec minimum)"
        self.params['time_pick_and_place_type'] = "automation"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params[
            'mttr_desc'] = "Mean time to repair (minutes) (0 to disable function)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)

        if (self.params['time_step'] < 1 / 10):  # enforce minimum time step
            self.params['time_step'] = 1 / 10

        if (self.params['time_pick_and_place'] <
                1 / 10):  # enforce minimum time step
            self.params['time_pick_and_place'] = 1 / 10

        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'])
        self.belt = collections.deque([False] *
                                      (self.params['units_on_belt'] + 1))
        self.output = BatchContainer(self.env, "output",
                                     self.params['stack_size'],
                                     self.params['max_stack_no'])

        self.downtime_finished = None
        self.technician_resource = simpy.Resource(self.env, 1)
        self.downtime_duration = 0
        self.maintenance_needed = False

        random.seed(self.params['random_seed'])

        self.mtbf_enable = False
        if (self.params['mtbf'] > 0) and (self.params['mttr'] > 0):
            self.next_failure = random.expovariate(
                1 / (3600 * self.params['mtbf']))
            self.mtbf_enable = True

        self.env.process(self.run_load_in_conveyor())
        self.env.process(self.run_pick_and_place())
Exemple #8
0
    def __init__(self, _env, _output=None, _params={}):
        QtCore.QObject.__init__(self)
        self.env = _env
        self.output_text = _output
        self.utilization = []
        self.diagram = """blockdiag {       
                       shadow_style = 'none';                      
                       default_shape = 'roundedbox';                       
                       A [label = "Input"];
                       B [label = "Belt0"];
                       C [label = "Printer0"];
                       D [label = "Dryer0"];                       
                       E [label = "Belt1"];
                       F [label = "Printer1"];
                       G [label = "Dryer1"];
                       H [label = "Belt2"];
                       I [label = "Printer2"];                     
                       J [label = "Firing furnace"];
                       K [label = "Output"];
                       A -> B -> C -> D -> E -> F -> G -> H -> I -> J -> K;
                       D -> E [folded];
                       H -> I [folded];
                       } """

        self.params = {}

        self.params['specification'] = """
<h3>General description</h3>
A print line is used for screen-printing metallization pastes and for drying and firing the pastes afterwards.
The machine accepts cassettes which are unloaded one wafer at a time.
Each wafer then travels to a number of printers and dryers, before entering a firing furnace.
Lastly, all units are placed in an infinitely sized container.\n
<h3>Description of the algorithm</h3>
There are two types of loops to simulate the print line tool.
The first one runs the wafer load-in process before the first printing step.
The second loop runs the printers, so there is a separate instance for each printer.
<p>The first loop for wafer load-in consists of the following steps:
<ol>
<li>Wait for signal from first printer</li>
<li>Pick up a wafer if not already available</li>
<li>If current cassette had been empty pause momentarily to simulate loading a new stack</li>
<li>If the first position on the belt is empty, load the wafer onto it</li>
</ol>
The printer loop performs the following actions if there is a wafer available at the end of its input belt:
<ol>
<li>Remove wafer from input belt and move the belt forward by one position</li>
<li>If the current printer is the first printer in the line, send a signal to the wafer load-in process to continue</li>
<li>Wait for a certain amount of time to simulate the printing action.
The waiting time can be the belt position movement time or the printing time, depending on which is higher.</li>
<li>Start a separate process for drying or firing</li>
</ol>
The drying or firing processes hold the wafer for a set amount of time to simulate these processes.
After a drying step the wafer is placed on the input belt of the next printer and after a firing step it is placed in an output container of infinite size.
<p>If there is no wafer availalbe for printing then the second loop performs the following actions:</p>
<ol>
<li>Move the input belt forward by one position</li>
<li>If the current printer is the first printer in the line, send a signal to the wafer load-in process to continue</li>
<li>Wait for a set amount of time to simulate the belt movement action</li>
</ol>
\n
        """

        self.params['name'] = ""
        self.params['type'] = "PrintLine"
        self.params['max_cassette_no'] = 8
        self.params[
            'max_cassette_no_desc'] = "Number of cassette positions at input"
        self.params['max_cassette_no_type'] = "configuration"
        self.params['time_new_cassette'] = 10
        self.params[
            'time_new_cassette_desc'] = "Time for putting an empty cassette into a loading position (seconds)"
        self.params['time_new_cassette_type'] = "automation"
        self.params['units_on_belt_input'] = 8
        self.params[
            'units_on_belt_input_desc'] = "Number of units that fit on the belt between wafer source and printer"
        self.params['units_on_belt_input_type'] = "configuration"

        self.params['time_step'] = 1.0
        self.params[
            'time_step_desc'] = "Time for one wafer to progress one position on belts (seconds)"
        self.params['time_step_type'] = "automation"
        self.params['time_print'] = 2.5
        self.params['time_print_desc'] = "Time to print one wafer (seconds)"
        self.params['time_print_type'] = "process"
        self.params['time_dry'] = 90
        self.params[
            'time_dry_desc'] = "Time for one wafer to go from printer to dryer and "
        self.params[
            'time_dry_desc'] += "to next input (printing or firing) (seconds)"
        self.params['time_dry_type'] = "process"

        self.params['no_print_steps'] = 3
        self.params['no_print_steps_desc'] = "Number of print and dry stations"
        self.params['no_print_steps_type'] = "configuration"

        self.params['firing_tool_length'] = 10.0
        self.params[
            'firing_tool_length_desc'] = "Travel distance for wafers in the last dryer and firing furnace (meters)"
        self.params['firing_tool_length_type'] = "configuration"
        self.params['firing_belt_speed'] = 5.0  # 5 is roughly 200 ipm
        self.params[
            'firing_belt_speed_desc'] = "Belt speed of last dryer and firing furnace (meters per minute)"
        self.params['firing_belt_speed_type'] = "process"

        self.params['unit_distance'] = 0.2
        self.params[
            'unit_distance_desc'] = "Minimal distance between wafers on firing furnace (meters)"
        self.params['unit_distance_type'] = "configuration"

        self.params['mtbf'] = 1000
        self.params[
            'mtbf_desc'] = "Mean time between failures (hours) (0 to disable function)"
        self.params['mtbf_type'] = "downtime"
        self.params['mttr'] = 60
        self.params[
            'mttr_desc'] = "Mean time to repair (minutes) (0 to disable function)"
        self.params['mttr_type'] = "downtime"
        self.params['random_seed'] = 42
        self.params['random_seed_type'] = "immutable"

        self.params['cassette_size'] = -1
        self.params['cassette_size_type'] = "immutable"

        self.params.update(_params)

        if self.output_text and self.params['cassette_size'] == -1:
            string = str(
                round(self.env.now, 1)
            ) + " [" + self.params['type'] + "][" + self.params['name'] + "] "
            string += "Missing cassette loop information"
            self.output_text.sig.emit(string)

        self.time_dry = self.params['time_dry']
        self.no_print_steps = self.params['no_print_steps']
        self.time_fire = int(60 * self.params['firing_tool_length'] //
                             self.params['firing_belt_speed'])

        ### Input ###
        self.input = CassetteContainer(self.env, "input",
                                       self.params['max_cassette_no'])

        ### Array of zeroes represents belts ###
        self.belts = []
        for i in range(self.params['no_print_steps']):
            self.belts.append(
                collections.deque([
                    False
                    for rows in range(self.params['units_on_belt_input'] + 1)
                ]))

        ### Infinite output container ###
        self.output = InfiniteContainer(self.env, "output")

        ### Check whether wafers will overlap ###
        time_out = []
        time_out.append(self.params['time_step'])
        time_out.append(self.params['time_print'])
        if (not (self.output_text
                 == None)) and (max(time_out) <
                                (60 * self.params['unit_distance'] /
                                 self.params['firing_belt_speed'])):
            string = "[" + self.params['type'] + "][" + self.params[
                'name'] + "] WARNING: Wafer distance in firing furnace below set minimum"
            self.output_text.sig.emit(string)

        self.idle_times_internal = []
        for i in range(self.params['no_print_steps']):
            self.idle_times_internal.append(0)

        self.downtime_finished = None
        self.technician_resource = simpy.Resource(self.env, 1)
        self.downtime_duration = 0
        self.maintenance_needed = False

        random.seed(self.params['random_seed'])

        self.mtbf_enable = False
        if (self.params['mtbf'] > 0) and (self.params['mttr'] > 0):
            self.next_failure = random.expovariate(
                1 / (3600 * self.params['mtbf']))
            self.mtbf_enable = True

        self.next_step = self.env.event()  # triggers load-in from cassette
        # start belt before printers; otherwise next_step will be triggered already
        # and a different one will be created
        self.env.process(self.run_belt())

        for i in range(self.params['no_print_steps']):
            self.env.process(self.run_printer(i))