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}