001/* An actor that interacts with gem5 architectural simulator 002 003 Copyright (c) 2017 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027*/ 028package ptolemy.actor.lib.gem5; 029 030import java.io.BufferedReader; 031import java.io.BufferedWriter; 032import java.io.File; 033import java.io.FileInputStream; 034import java.io.FileNotFoundException; 035import java.io.FileOutputStream; 036import java.io.FileReader; 037import java.io.IOException; 038import java.io.InputStreamReader; 039import java.io.OutputStreamWriter; 040import java.io.Serializable; 041import java.util.ArrayList; 042import java.util.Collections; 043import java.util.Comparator; 044import java.util.StringTokenizer; 045 046import ptolemy.actor.lib.SequenceSource; 047import ptolemy.actor.parameters.PortParameter; 048import ptolemy.data.ArrayToken; 049import ptolemy.data.BooleanToken; 050import ptolemy.data.IntToken; 051import ptolemy.data.RecordToken; 052import ptolemy.data.StringToken; 053import ptolemy.data.Token; 054import ptolemy.data.expr.Parameter; 055import ptolemy.data.expr.StringParameter; 056import ptolemy.data.type.ArrayType; 057import ptolemy.data.type.BaseType; 058import ptolemy.data.type.RecordType; 059import ptolemy.data.type.Type; 060import ptolemy.kernel.CompositeEntity; 061import ptolemy.kernel.util.Attribute; 062import ptolemy.kernel.util.IllegalActionException; 063import ptolemy.kernel.util.InternalErrorException; 064import ptolemy.kernel.util.NameDuplicationException; 065import ptolemy.kernel.util.Workspace; 066 067/////////////////////////////////////////////////////////////////// 068//// Gem5Wrapper 069 070/** 071 * An actor that interacts with gem5 architectural simulator. 072 * 073 * <p>The <a href="http://gem5.org/#in_browser">gem5 simulator</a> 074 * "is a modular platform for computer-system architecture 075 * research."</p> 076 * 077 * @author Hokeun Kim, contributor: Christopher Brooks 078 * @version $Id: Gem5Wrapper.java 67679 2013-10-13 03:48:10Z cxh $ 079 * @since Ptolemy II 11.0 080 * @Pt.ProposedRating Red (cxh) 081 * @Pt.AcceptedRating Red (cxh) 082 */ 083public class Gem5Wrapper extends SequenceSource { 084 // FIXME: Why does this actor have an init and a step? It seems 085 // like copy and paste from Ramp? 086 087 /** Construct an actor with the given container and name. 088 * In addition to invoking the base class constructors, construct 089 * the <i>init</i> and <i>step</i> parameter and the <i>step</i> 090 * port. Initialize <i>init</i> 091 * to IntToken with value 0, and <i>step</i> to IntToken with value 1. 092 * @param container The container. 093 * @param name The name of this actor. 094 * @exception IllegalActionException If the actor cannot be contained 095 * by the proposed container. 096 * @exception NameDuplicationException If the container already has an 097 * actor with this name. 098 */ 099 public Gem5Wrapper(CompositeEntity container, String name) 100 throws NameDuplicationException, IllegalActionException { 101 super(container, name); 102 103 // FIXME: init and step should go away. 104 init = new PortParameter(this, "init"); 105 init.setExpression("0"); 106 new Parameter(init.getPort(), "_showName", BooleanToken.TRUE); 107 108 pipePathPrefix = new StringParameter(this, "pipePathPrefix"); 109 pipePathPrefix.setExpression(""); 110 111 step = new PortParameter(this, "step"); 112 step.setExpression("1"); 113 new Parameter(step.getPort(), "_showName", BooleanToken.TRUE); 114 115 // set the type constraints. 116 //ArrayType arrayOfCommandsType = new ArrayType(BaseType.GENERAL, 318); 117 RecordType recordType = new RecordType(_labels, _types); 118 ArrayType arrayOfRecordsType = new ArrayType(recordType); 119 output.setTypeEquals(arrayOfRecordsType); 120 output.setAutomaticTypeConversion(false); 121 //output.setTypeEquals(BaseType.Arra); 122 123 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" " 124 + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n" 125 + "<polygon points=\"-20,10 20,-10 20,10\" " 126 + "style=\"fill:grey\"/>\n" + "</svg>\n"); 127 128 // Show the firingCountLimit parameter last. 129 firingCountLimit.moveToLast(); 130 _tempPipe = null; 131 _process = null; 132 _readPipe = null; 133 } 134 135 /////////////////////////////////////////////////////////////////// 136 //// ports and parameters //// 137 138 // FIXME: init and step should go away. 139 140 /** The value produced by the ramp on its first iteration. 141 * If this value is changed during execution, then the new 142 * value will be the output on the next iteration. 143 * The default value of this parameter is the integer 0. 144 */ 145 public PortParameter init; 146 147 /** The prefix of the file path for the pipe used for communicating 148 * with gem5 simulator. 149 */ 150 public StringParameter pipePathPrefix; 151 152 /** The amount by which the ramp output is incremented on each iteration. 153 * The default value of this parameter is the integer 1. 154 */ 155 public PortParameter step; 156 157 /////////////////////////////////////////////////////////////////// 158 //// public methods //// 159 160 /** If the argument is the <i>init</i> parameter, then reset the 161 * state to the specified value. 162 * @param attribute The attribute that changed. 163 * @exception IllegalActionException If <i>init</i> cannot be evaluated 164 * or cannot be converted to the output type, or if the superclass 165 * throws it. 166 */ 167 @Override 168 public void attributeChanged(Attribute attribute) 169 throws IllegalActionException { 170 super.attributeChanged(attribute); 171 } 172 173 /** Clone the actor into the specified workspace. This calls the 174 * base class and then sets the <code>init</code> and <code>step</code> 175 * public members to the parameters of the new actor. 176 * @param workspace The workspace for the new object. 177 * @return A new actor. 178 * @exception CloneNotSupportedException If a derived class contains 179 * an attribute that cannot be cloned. 180 */ 181 @Override 182 public Object clone(Workspace workspace) throws CloneNotSupportedException { 183 Gem5Wrapper newObject = (Gem5Wrapper) super.clone(workspace); 184 185 // FIXME: init and step should go away. 186 // set the type constraints. 187 // newObject.output.setTypeAtLeast(newObject.init); 188 // newObject.output.setTypeAtLeast(newObject.step); 189 190 RecordType recordType = new RecordType(_labels, _types); 191 ArrayType arrayOfRecordsType = new ArrayType(recordType); 192 output.setTypeEquals(arrayOfRecordsType); 193 output.setAutomaticTypeConversion(false); 194 195 return newObject; 196 } 197 198 private ArrayToken getGem5SimResult() throws IllegalActionException { 199 try { 200 int line; 201 while (true) { 202 char[] buffer = new char[256]; 203 line = _writePipe.read(buffer); 204 if (line != -1) { 205 break; 206 } 207 } 208 } catch (IOException ex) { 209 throw new IllegalActionException(this, ex, "Failed to read " 210 + pipePathPrefix.getValueAsString() + "/write_pipe"); 211 } 212 StringBuilder result = new StringBuilder(); 213 ArrayList<RecordToken> tokenArray = new ArrayList<RecordToken>(); 214 try { 215 String line = _tempPipe.readLine(); 216 while (line != null) { 217 if (line.contains("PTOLEMY_LOG")) { 218 Token tokens[] = new Token[_labels.length]; 219 //StringToken[] tuple = new StringToken[2]; 220 StringTokenizer strTokenizer = new StringTokenizer(line); 221 String command = ""; 222 int commandTime = 0; 223 int serviceTime = 0; 224 int rankNumber = -1; 225 int bankNumber = -1; 226 int initTime = 0; 227 boolean isFirstToken = true; 228 boolean isCommand = false; 229 boolean isRank = false; 230 boolean isBank = false; 231 while (strTokenizer.hasMoreTokens()) { 232 String curToken = strTokenizer.nextToken(); 233 if (isFirstToken) { 234 isFirstToken = false; 235 236 long cpuInitTime = Long.parseLong(curToken 237 .substring(0, curToken.length() - 1)); 238 initTime = (int) ((cpuInitTime + _systemClockPeriod) 239 / _systemClockPeriod); // in ns 240 serviceTime = initTime % _sampleTime; 241 } else if (curToken.contains("Rank")) { 242 isRank = true; 243 } else if (isRank) { 244 isRank = false; 245 rankNumber = Integer.parseInt( 246 curToken.substring(0, curToken.length())); 247 } else if (curToken.contains("Bank")) { 248 isBank = true; 249 } else if (isBank) { 250 isBank = false; 251 bankNumber = Integer.parseInt( 252 curToken.substring(0, curToken.length())); 253 } else if (curToken.contains("PRE") 254 || curToken.contains("ACT") 255 || curToken.contains("READ") 256 || curToken.contains("WRITE")) { 257 isCommand = true; 258 command = new String(curToken.substring(0, 259 curToken.length() - 1)); 260 } else if (isCommand) { 261 isCommand = false; 262 // from previous delay 263 int delayDiff = Integer.parseInt(curToken); 264 delayDiff = ((delayDiff + _systemClockPeriod) 265 / _systemClockPeriod); // in ns 266 commandTime += delayDiff; 267 tokens[0] = new StringToken(command); 268 tokens[1] = new IntToken(initTime + commandTime); 269 tokens[2] = new IntToken(rankNumber); 270 tokens[3] = new IntToken(bankNumber); 271 tokens[4] = new IntToken(serviceTime + commandTime); 272 273 tokenArray.add(new RecordToken(_labels, tokens)); 274 //tokenArray.add(new ArrayToken(BaseType.STRING,tuple)); 275 } 276 } 277 result.append(line); 278 result.append(System.lineSeparator()); 279 } 280 line = _tempPipe.readLine(); 281 } 282 //everything = result.toString(); 283 } catch (IOException ex) { 284 throw new IllegalActionException(this, ex, 285 "Failed to get the simulation results from Gem5."); 286 } 287 288 Collections.sort(tokenArray, new SortByCommandTime()); 289 290 //StringToken stringToken = new StringToken("*************Iteration Count: " + _iterationCount + "\n" + result.toString()); 291 Token[] dummy = new Token[0]; 292 if (tokenArray.isEmpty()) { 293 return null; 294 } else { 295 return new ArrayToken(tokenArray.toArray(dummy)); 296 } 297 } 298 299 /** Send the current value of the state of this actor to the output. 300 * @exception IllegalActionException If calling send() or super.fire() 301 * throws it. 302 */ 303 @Override 304 public void fire() throws IllegalActionException { 305 super.fire(); 306 307 ArrayToken simulationResults = getGem5SimResult(); 308 if (simulationResults != null) { 309 output.send(0, simulationResults); 310 } 311 _iterationCount++; 312 } 313 314 /** Set the state to equal the value of the <i>init</i> parameter. 315 * The state is incremented by the value of the <i>step</i> 316 * parameter on each iteration (in the postfire() method). 317 * @exception IllegalActionException If the parent class throws it. 318 */ 319 @Override 320 public void initialize() throws IllegalActionException { 321 super.initialize(); 322 323 //String pipePathPrefix = "/Users/hokeunkim/Development/ee219dproject/gem5-stable_2015_09_03/"; 324 try { 325 if (_process != null) { 326 _process.destroy(); 327 _process = null; 328 _readPipe = null; 329 _writePipe = null; 330 } 331 332 String outputFileName = pipePathPrefix.getValueAsString() 333 + "/read_pipe"; 334 _readPipe = new BufferedWriter(new OutputStreamWriter( 335 new FileOutputStream(new File(outputFileName)))); 336 337 _readPipe.newLine(); 338 _readPipe.flush(); 339 340 String inputFileName = pipePathPrefix.getValueAsString() 341 + "/write_pipe"; 342 _writePipe = new InputStreamReader( 343 new FileInputStream(new File(inputFileName))); 344 //_process = pb.start(); 345 } catch (IOException ex) { 346 throw new IllegalActionException(this, ex, 347 "Failed create the read or write pipe to gem5."); 348 } 349 350 try { 351 if (_tempPipe != null) { 352 try { 353 _tempPipe.close(); 354 } catch (IOException ex) { 355 throw new IllegalActionException(this, ex, 356 "Failed to close the temporary pipe."); 357 } 358 } 359 _tempPipe = new BufferedReader(new FileReader( 360 pipePathPrefix.getValueAsString() + "/temp_pipe")); 361 } catch (FileNotFoundException ex) { 362 throw new InternalErrorException(this, ex, "Failed to create " 363 + pipePathPrefix.getValueAsString() + "/temp_pipe"); 364 } 365 } 366 367 // FIXME: init and step should go away and this comment updated. 368 369 /** Update the state of the actor by adding the value of the 370 * <i>step</i> parameter to the state. Also, increment the 371 * iteration count, and if the result is equal to 372 * <i>firingCountLimit</i>, then 373 * return false. 374 * @return False if the number of iterations matches the number requested. 375 * @exception IllegalActionException If the firingCountLimit parameter 376 * has an invalid expression. 377 */ 378 @Override 379 public boolean postfire() throws IllegalActionException { 380 381 // FIXME: If the init and step PortParameters remain, this 382 // method should update _stateToken like in Ramp. 383 384 //_stateToken = _stateToken.add(step.getToken()); 385 386 try { 387 _readPipe.newLine(); 388 _readPipe.flush(); 389 } catch (IOException ex) { 390 throw new IllegalActionException(this, ex, 391 "Failed to close or flush the " 392 + pipePathPrefix.getValueAsString() + "/read_pipe"); 393 } 394 return super.postfire(); 395 } 396 397 /////////////////////////////////////////////////////////////////// 398 //// private variables //// 399 400 private static String[] _labels = { "cmd", "cmd_time", "rank", "bank", 401 "service_time" }; 402 403 /** The pipePathPrefix/temp_pipe. */ 404 private BufferedReader _tempPipe; 405 406 private Process _process; 407 408 /** The pipePathPrefix/read_pipe. */ 409 private BufferedWriter _readPipe; 410 411 private int _systemClockPeriod = 1000; 412 private int _sampleTime = 500 * 1000; // 0.5 ms 413 414 private static Type[] _types = { BaseType.STRING, BaseType.INT, 415 BaseType.INT, BaseType.INT, BaseType.INT }; 416 417 /** The pipePathPrefix/write_pipe. */ 418 private InputStreamReader _writePipe; 419 420 /** Sort by the difference between the command times. */ 421 public static class SortByCommandTime 422 implements Comparator<RecordToken>, Serializable { 423 /** Return the difference between time 1 and time2. 424 * @param t1 The record token containing the first time. 425 * @param t2 The record token containing the first time. 426 * @return The difference between the two times. 427 */ 428 @Override 429 public int compare(RecordToken t1, RecordToken t2) { 430 int time1 = ((IntToken) t1.get(_labels[1])).intValue(); 431 int time2 = ((IntToken) t2.get(_labels[1])).intValue(); 432 return (time1 - time2); 433 } 434 } 435}