1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using System.Collections.Generic;
10 using System.Linq;
11 using Antmicro.Renode.Utilities;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Peripherals.CPU.Disassembler;
14 using Google.FlatBuffers;
15 using FBInstruction; // <auto-generated with flatc -csharp>
16 using Antmicro.Renode.Logging.Profiling;
17 
18 namespace Antmicro.Renode.Peripherals.CPU
19 {
20     public class TraceBasedModelFlatBufferWriter : TraceWriter
21     {
TraceBasedModelFlatBufferWriter(TranslationCPU cpu, string path, TraceFormat format, bool compress)22         public TraceBasedModelFlatBufferWriter(TranslationCPU cpu, string path, TraceFormat format, bool compress)
23             : base(cpu, path, format, compress)
24         {
25             disassemblyCache = new LRUCache<uint, Disassembler.DisassemblyResult>(CacheSize);
26             instructionsBuffer = new List<InstructionTrace>();
27             vectorConfig = new Tuple<float, byte, short>(0, 0, -1);
28         }
29 
Write(ExecutionTracer.Block block)30         public override void Write(ExecutionTracer.Block block)
31         {
32             var pc = block.FirstInstructionPC;
33             var pcVirtual = block.FirstInstructionVirtualPC;
34             var counter = 0;
35             var hasAdditionalData = block.AdditionalDataInTheBlock.TryDequeue(out var nextAdditionalData);
36 
37             while(counter < (int)block.InstructionsCount)
38             {
39                 if(!TryReadAndDisassembleInstruction(pc, block.DisassemblyFlags, out var result))
40                 {
41                     break;
42                 }
43 
44                 var additionalData = new List<AdditionalData>();
45                 while(hasAdditionalData && (nextAdditionalData.PC == pcVirtual))
46                 {
47                     additionalData.Add(nextAdditionalData);
48                     hasAdditionalData = block.AdditionalDataInTheBlock.TryDequeue(out nextAdditionalData);
49                 }
50 
51                 var opcode = Misc.HexStringToByteArray(result.OpcodeString.Trim(), true);
52 
53                 instructionsBuffer.Add(new InstructionTrace(result, opcode, additionalData));
54 
55                 pc += (ulong)result.OpcodeSize;
56                 pcVirtual += (ulong)result.OpcodeSize;
57                 counter++;
58             }
59             FlushBuffer();
60         }
61 
FlushBuffer()62         public override void FlushBuffer()
63         {
64             var builder = new FlatBufferBuilder(InitialFlatBufferSize);
65             var instructions = instructionsBuffer.Select(x => BuildInstructionFlatbuffer(builder, x.Result, x.Opcode, x.AdditionalData)).ToArray();
66             var instrsVector = Instructions.CreateInstructionsVector(builder, instructions);
67             Instructions.StartInstructions(builder);
68             Instructions.AddInstructions(builder, instrsVector);
69             var instrs = Instructions.EndInstructions(builder);
70             Instructions.FinishInstructionsBuffer(builder, instrs);
71 
72             var buf = builder.DataBuffer.ToSizedArray();
73             // Write the size of the buffer
74             stream.Write(BitConverter.GetBytes(buf.Length), 0, sizeof(int));
75             stream.Write(buf, 0, buf.Length);
76             stream.Flush();
77 
78             builder.Clear();
79             instructionsBuffer.Clear();
80         }
81 
Dispose(bool disposing)82         protected override void Dispose(bool disposing)
83         {
84             if(disposed)
85             {
86                 return;
87             }
88 
89             if(disposing)
90             {
91                 FlushBuffer();
92                 stream?.Dispose();
93             }
94             disposed = true;
95         }
96 
BuildInstructionFlatbuffer(FlatBufferBuilder builder, DisassemblyResult result, byte[] opcode, List<AdditionalData> additionalData)97         private Offset<Instruction> BuildInstructionFlatbuffer(FlatBufferBuilder builder, DisassemblyResult result, byte[] opcode, List<AdditionalData> additionalData)
98         {
99             if(result.DisassemblyString == null)
100             {
101                 // return a default instruction if the disassembly failed
102                 Logger.Log(LogLevel.Warning, $"Disassembly is not available for the instruction at 0x{result.PC:X}. Returning default TBM instruction.");
103                 return Instruction.CreateInstruction(builder);
104             }
105 
106             var disasString = result.DisassemblyString.Trim();
107             var sepIdx = disasString.IndexOf('\t');
108 
109             var mnemonic = "";
110             var operands = new string[0];
111 
112             if(sepIdx == -1)
113             {
114                 mnemonic = disasString;
115             }
116             else
117             {
118                 mnemonic = disasString.Substring(0, sepIdx);
119                 var operandsString = disasString.Substring(sepIdx + 1);
120                 operands = operandsString.Split(new string[] { ", " }, StringSplitOptions.None);
121             }
122 
123             var mnemonicOffset = builder.CreateString(mnemonic);
124 
125             var operandStringOffsetVector = operands.Select(x => builder.CreateString(x)).ToArray();
126             var operandsVector = Instruction.CreateOperandsVector(builder, operandStringOffsetVector);
127 
128             var inputsAndOutputs = TBMRiscVHelper.AsmRegisters(mnemonic, operands);
129             var inputsStringOffsetVector = inputsAndOutputs.Item1.Select(x => builder.CreateString(x)).ToArray();
130             var outputsStringOffsetVector = inputsAndOutputs.Item2.Select(x => builder.CreateString(x)).ToArray();
131             var inputsVector = Instruction.CreateInputsVector(builder, inputsStringOffsetVector);
132             var outputsVector = Instruction.CreateOutputsVector(builder, outputsStringOffsetVector);
133 
134             var stores = new List<ulong>();
135             var loads = new List<ulong>();
136 
137             // Execution tracer accesses additional data through hooks mounted at a given PC.
138             // Available types of data include memory access and vector configuration.
139             foreach(var additionalDataEntry in additionalData)
140             {
141                 switch(additionalDataEntry.Type)
142                 {
143                     case AdditionalDataType.MemoryAccess:
144                         var type = (additionalDataEntry as MemoryAccessAdditionalData).OperationType;
145                         var address = (additionalDataEntry as MemoryAccessAdditionalData).OperationTargetVirtual;
146                         switch(type)
147                         {
148                             case MemoryOperation.MemoryWrite:
149                             case MemoryOperation.MemoryIOWrite:
150                                 stores.Add(address);
151                                 break;
152                             case MemoryOperation.MemoryRead:
153                             case MemoryOperation.MemoryIORead:
154                                 loads.Add(address);
155                                 break;
156                             case MemoryOperation.InsnFetch: // the instruction fetch is not included according to the documentation
157                             default:
158                                 break;
159                         }
160                         break;
161                     case AdditionalDataType.RiscVVectorConfiguration:
162                         // Single instruction should have maximum one entry of vector configuration type
163                         // so it shouldn't overwrite the previous value.
164 
165                         var vtype = (additionalDataEntry as RiscVVectorConfigurationData).VectorType;
166                         // vector length multiplier
167                         var vlmul = BitHelper.GetValue(vtype, 0, 3);
168                         var lmul = TBMRiscVHelper.GetVectorLengthMultiplier(vlmul);
169                         // element width
170                         var vsew = BitHelper.GetValue(vtype, 3, 3);
171                         var sew = TBMRiscVHelper.GetSelectedElementWidth(vsew);
172                         // vector length
173                         var vl = (short)(additionalDataEntry as RiscVVectorConfigurationData).VectorLength;
174                         // Keep the current vector configuration to pass it to other vector instructions.
175                         vectorConfig = new Tuple<float, byte, short>(lmul, sew, vl);
176                         break;
177                     case AdditionalDataType.None:
178                     default:
179                         break;
180                 }
181             }
182 
183             var isVectorInstruction = TBMRiscVHelper.IsVectorInstruction(inputsAndOutputs.Item1, inputsAndOutputs.Item2);
184 
185             var storesVector = Instruction.CreateStoresVector(builder, stores.ToArray());
186             var loadsVector = Instruction.CreateLoadsVector(builder, loads.ToArray());
187 
188             Instruction.StartInstruction(builder);
189             Instruction.AddAddr(builder, result.PC);
190             Instruction.AddOpcode(builder, BitHelper.ToUInt32(opcode, 0, opcode.Length, false));
191             Instruction.AddMnemonic(builder, mnemonicOffset);
192             Instruction.AddOperands(builder, operandsVector);
193             Instruction.AddInputs(builder, inputsVector);
194             Instruction.AddOutputs(builder, outputsVector);
195             Instruction.AddIsNop(builder, TBMRiscVHelper.IsNop(mnemonic));
196             Instruction.AddIsBranch(builder, TBMRiscVHelper.IsBranch(mnemonic));
197             Instruction.AddBranchTarget(builder, 0);
198             Instruction.AddIsFlush(builder, TBMRiscVHelper.IsFlush(mnemonic));
199             Instruction.AddIsVctrl(builder, TBMRiscVHelper.IsVctrl(mnemonic));
200             Instruction.AddLoads(builder, loadsVector);
201             Instruction.AddStores(builder, storesVector);
202             Instruction.AddLmul(builder, isVectorInstruction ? vectorConfig.Item1 : 0);
203             Instruction.AddSew(builder, isVectorInstruction ? vectorConfig.Item2 : (byte)0);
204             Instruction.AddVl(builder, isVectorInstruction ? vectorConfig.Item3 : (short)-1);
205 
206             return Instruction.EndInstruction(builder);
207         }
208 
209         private bool disposed;
210         private readonly List<InstructionTrace> instructionsBuffer;
211         private Tuple<float, byte, short> vectorConfig;
212 
213         private const int InitialFlatBufferSize = 1024;
214         private const int CacheSize = 100000;
215 
216         private class InstructionTrace
217         {
InstructionTrace(DisassemblyResult result, byte[] opcode, List<AdditionalData> additionalData)218             public InstructionTrace(DisassemblyResult result, byte[] opcode, List<AdditionalData> additionalData)
219             {
220                 Result = result;
221                 Opcode = opcode;
222                 AdditionalData = additionalData;
223             }
224 
225             public DisassemblyResult Result { get; }
226             public byte[] Opcode { get; }
227             public List<AdditionalData> AdditionalData { get; }
228         }
229     }
230 }
231