func printHeader() { logger.Print("") logger.Print("################################################") logger.Print("# Superscalar Processor Simulator v1.0 #") logger.Print("################################################") logger.Print("") }
func (this *Decoder) Run(input, output channel.Channel) { // Launch each unit as a goroutine logger.Print(" => Initializing decoder unit %d", this.Index()) go func() { for { value, running := <-input.Channel() if !running || !this.IsActive() { logger.Print(" => Flushing decoder unit %d", this.Index()) return } op := operation.Cast(value) // Iterate instructions received via the channel instruction, err := this.decodeInstruction(op) if err != nil { logger.Error(err.Error()) break } // Send data to output op.SetInstruction(instruction) output.Add(op) // Release one item from input Channel input.Release() } }() }
func (this *Executor) Run(input map[info.CategoryEnum]channel.Channel, commonDataBus channel.Channel) { // Launch each unit as a goroutine unit, event := this.getUnitFromCategory(this.Category()) logger.Print(" => Initializing execution unit (%s) %d", this.Category(), this.Index()) go func() { for { value, running := <-input[this.Category()].Channel() if !running || !this.IsActive() { logger.Print(" => Flushing execution unit (%s) %d", this.Category(), this.Index()) return } op := operation.Cast(value) for i := uint8(0); i < op.Instruction().Info.Cycles; i++ { this.Processor().Wait(1) } // Iterate instructions received via the channel op, _ = this.executeOperation(unit, event, op) // Send data to common bus for reservation station feedback commonDataBus.Add(op) // Release one item from input Channel input[this.Category()].Release() } }() }
func (this *Processor) NextCycle() int { if this.FinishedClock() { logger.Print(" => Program has finished\n") return consts.PROGRAM_FINISHED } this.WaitClock() return consts.PROGRAM_RUNNING }
func (this *Processor) Start() { logger.CleanBuffer() logger.Print("\n Simulation:") logger.Print("\n => Starting program...") // Launch pipeline units and execute instruction 0x0000 recoveryChannel := channel.New(1) flushFunc := this.StartPipelineUnits(this.Config(), recoveryChannel, 0, 0x0000) // Launch clock go this.RunClock() // Launch recovery go this.RunRecovery(recoveryChannel, flushFunc) logger.Print(" => Program is running...") logger.SetVerboseQuiet(true) }
func (this *Fetcher) Run(input, output channel.Channel) { logger.Print(" => Initializing fetcher unit %d", this.Index()) // Launch each unit as a goroutine go func() { for { value, running := <-input.Channel() if !running || !this.IsActive() { logger.Print(" => Flushing fetcher unit %d", this.Index()) return } // Release item from input Channel input.Release() // Initial operation (address) op := operation.Cast(value) // Load instructions data from memory data := this.Processor().InstructionsMemory().Load(op.Address(), consts.BYTES_PER_WORD*this.InstructionsFetchedPerCycle()) // Fetch instructions startCycles := this.Processor().Cycles() operations, err := this.fetchInstructions(op, data, input) if err != nil { logger.Error(err.Error()) break } // Wait cycles of a fetch stage this.Processor().Wait(consts.FETCH_CYCLES) // After wait cycle, notify decode channel with new operations for _, op := range operations { if this.IsActive() { this.Processor().LogEvent(consts.FETCH_EVENT, this.Index(), op.Id(), startCycles) output.Add(op) } } } }() }
func TranslateFromFile(filename string, outputFilename string) (string, error) { // Read lines from file logger.Print(" => Reading assembly file: %s", filename) lines, err := utils.ReadLines(filename) if err != nil { return "", err } // Create output file f, err := os.Create(outputFilename) if err != nil { return "", err } defer f.Close() // Clean lines, remove labels and get map of labels memory, lines, labels := getLinesAndMapLabels(lines) // Print pre-filled memory macros for _, line := range memory { f.WriteString(fmt.Sprintf("%s\n", line)) } // Translate instructions instructionSet := set.Init() for i, line := range lines { instruction, err := instructionSet.GetInstructionFromString(line, uint32(i)*consts.BYTES_PER_WORD, labels) if err != nil { return "", errors.New(fmt.Sprintf("Failed translating line %d: %s. %s", i, line, err.Error())) } hex := fmt.Sprintf("%08X", instruction.ToUint32()) f.WriteString(fmt.Sprintf("%s // 0x%04X => %s\n", hex, i*consts.BYTES_PER_WORD, strings.Replace(line, "\t", " ", -1))) } logger.Print(" => Output hex file: %s", outputFilename) return outputFilename, nil }
func (this *ReservationStation) Run(commonDataBus channel.Channel, output map[info.CategoryEnum]channel.Channel) { // Launch unit as a goroutine logger.Print(" => Initializing reservation station unit %d", this.Index()) this.SetOutput(output) go func() { for this.reservationStation.isActive { this.Processor().Wait(consts.DISPATCH_CYCLES) this.reservationStation.instructionsDispatchedStack = 0 } }() go this.runScheduler() go this.runCommonBusListener(commonDataBus) }
func (this *Dispatcher) runCommonBusMultiplexer(input, output1, output2 channel.Channel) { // For each result got from execution units in the common data bus send to RS and ROB for { value, running := <-input.Channel() if !running { output1.Close() output2.Close() logger.Print(" => Flushing dispatcher unit %d (CDB Mux)", this.Index()) return } output1.Add(value) output2.Add(value) input.Release() } }
func (this *Dispatcher) runDispatcherToReservationStation(input channel.Channel, rs *reservationstation.ReservationStation, rat *registeraliastable.RegisterAliasTable, rob *reorderbuffer.ReorderBuffer) { incomingQueue := map[uint32]*operation.Operation{} currentOperationId := this.StartOperationId() // For each operation received to schedule, process it for { value, running := <-input.Channel() if !running || !this.IsActive() { logger.Print(" => Flushing dispatcher unit %d (dispatcher to RS)", this.Index()) return } op := operation.Cast(value) // Add to current operation incomingQueue[op.Id()] = op // Send to incoming channel pending ops (if available) for op, exists := incomingQueue[currentOperationId]; exists; op, exists = incomingQueue[currentOperationId] { // Allocate in ROB if there is spacde, otherwise stall rob.Allocate(op) // Rename register in case of WAR & WAR hazards if this.RegisterAliasTableEntries() > 0 { _, destRegister := rs.GetDestinationDependency(op.Id(), op.Instruction()) if destRegister != -1 { found, _ := rat.AddMap(uint32(destRegister), op.Id()) if !found { // Need to stall for an available RAT entry logger.Collect(" => [DI%d][%03d]: No entry available in RAT. Wait for one...", this.Index(), op.Id()) break } // Rename to physical registers this.renameRegisters(op.Id(), op, rat) } } //Redirect input operations to the required execution unit channels logger.Collect(" => [DI%d][%03d]: Scheduling to RS: %s, %s", this.Index(), op.Id(), op.Instruction().Info.ToString(), op.Instruction().Data.ToString()) rs.Schedule(op) currentOperationId += 1 } input.Release() } }
func (this *Processor) loadInstructionsMemory(assemblyFileName string) error { logger.Print(" => Reading hex file: %s", assemblyFileName) lines, err := utils.ReadLines(assemblyFileName) if err != nil { return err } address := uint32(0) for _, line := range lines { // Split hex value and humand readable comment parts := strings.Split(line, "//") // Save human readable for debugging purposes if len(parts) > 1 { this.InstructionsMap()[address] = strings.TrimSpace(parts[1]) } // Reach pre-filled memory data lines if strings.Contains(line, "@0x") { parts := strings.Split(strings.Replace(line, "@0x", "", -1), ":") err = this.processPreFilledDataMemoryLine(parts[0], strings.Split(parts[1], " ")) if err != nil { return errors.New(fmt.Sprintf("Failed parsing memory macro. %s", err.Error())) } continue } // Save hex value into instructions memory bytes, err := hex.DecodeString(strings.TrimSpace(parts[0])) if err != nil { return errors.New(fmt.Sprintf("Failed parsing instruction (hex) value: %s. %s", parts[0], err.Error())) } this.InstructionsMemory().Store(address, bytes...) // Increment address address += uint32(len(bytes)) } for i := address; i < this.InstructionsMemory().Size(); i++ { this.InstructionsMemory().Store(i, []byte{consts.ENDING_BYTE}...) } return nil }
func runProgram(assemblyFilename string, interactive bool, outputFolder string, config *config.Config, maxCycles uint32) error { err := os.MkdirAll(outputFolder, 0777) if err != nil { return err } // Translate assembly file to hex file hexFilename, err := translator.TranslateFromFile(assemblyFilename, filepath.Join(outputFolder, "assembly.hex")) if err != nil { return err } // Instanciate processor p, err := processor.New(hexFilename, config) if err != nil { return err } // Start simulation p.Start() // Run as many instructions as they are result := consts.PROGRAM_RUNNING for result == consts.PROGRAM_RUNNING { p.PauseClock() if interactive { for runInteractiveStep(p) { } } // Unpause and execute next cycle p.ContinueClock() result = p.NextCycle() // If max cycles option selected if maxCycles > 0 && p.Cycles() >= maxCycles { p.PauseClock() break } } logger.Print(p.Stats()) return p.SaveOutputFiles(outputFolder) }
func New(assemblyFileName string, config *config.Config) (*Processor, error) { p := &Processor{ &processor{ done: false, instructionsFetched: []string{}, instructionsCompleted: []uint32{}, dataLog: map[uint32][]LogEvent{}, branchHistoryTable: map[uint32]uint32{}, conditionalBranches: 0, unconditionalBranches: 0, mispredictedBranches: 0, noTakenBranches: 0, branchPredictorBits: 0, speculativeJumps: 0, instructionsMap: map[uint32]string{}, instructionsSet: set.Init(), config: config, programCounter: 0, registerMemory: memory.New(config.RegistersMemorySize()), instructionMemory: memory.New(config.InstructionsMemorySize()), dataMemory: memory.New(config.DataMemorySize()), }, } logger.Print(config.ToString()) // Instanciate functional units instructionsFinished := func() bool { return p.processor.done && p.InstructionsFetchedCounter() == p.InstructionsCompletedCounter() && p.SpeculativeJumps() == 0 } p.processor.clockUnit = clock.New(config.CyclePeriod(), instructionsFinished) err := p.loadInstructionsMemory(assemblyFileName) if err != nil { return p, err } return p, nil }
func (this *ReservationStation) runCommonBusListener(commonDataBus channel.Channel) { // For each operation executed, feed reservation station to release operands for { value, running := <-commonDataBus.Channel() if !running { this.reservationStation.isActive = false logger.Print(" => Flushing reservation station unit %d (CDB listener)", this.Index()) return } op := operation.Cast(value) commonDataBus.Release() this.Lock() <- true dest, _, _ := this.getComponentsFromInstruction(op.Instruction()) entryIndex := this.getEntryIndexFromOperationId(op.Id()) entryOp := this.Entries()[entryIndex] logger.Collect(" => [RS%d][%03d]: Operation completed, releasing entry %d", this.Index(), op.Id(), entryIndex) if entryIndex != INVALID_INDEX { // Release entry this.Entries()[entryIndex].Busy = false this.Entries()[entryIndex].Free = true // Release entry from reservation station queue this.Input().Release() } // Release destination register (as RAT if enabled) if dest != INVALID_INDEX { logger.Collect(" => [RS%d][%03d]: Register %v resolved", this.Index(), op.Id(), entryOp.Destination) this.releaseOperation(entryOp.Destination) } // Release operands registers for _, operand := range entryOp.Operands { if operand.IsValid() && (operand.Type == MemoryType || len(this.RegisterAliasTable().Entries()) == 0) { logger.Collect(" => [RS%d][%03d]: Register %v resolved", this.Index(), op.Id(), operand) this.releaseOperation(operand) } } <-this.Lock() } }
func runCommand(c *cli.Context) { printHeader() logger.SetVerboseDebug(c.Bool("verbose")) if len(c.Args()) != 1 { logger.Error("Expecting <assembly-filename> and got %d parameters", len(c.Args())) os.Exit(1) } assemblyFilename, _ := filepath.Abs(c.Args()[0]) if _, err := os.Stat(assemblyFilename); os.IsNotExist(err) { logger.Error("File %s does not exists", assemblyFilename) os.Exit(1) } outputFolder, _ := filepath.Abs(c.String("output-folder")) if outputFolder == "" { outputFolder = filepath.Join(filepath.Dir(assemblyFilename), getFileName(assemblyFilename)) } configFilename, _ := filepath.Abs(c.String("config-filename")) if configFilename == "" { logger.Error("Configuration file not provided, please provide a valid configuration file") os.Exit(1) } cfg, err := config.Load(configFilename) if err != nil { logger.Error("Failed loading config. %s", err.Error()) os.Exit(1) } logger.Print(" => Configuration file: %s", configFilename) err = runProgram(assemblyFilename, c.Bool("step-by-step"), outputFolder, cfg, uint32(c.Int("max-cycles"))) if err != nil { logger.Error(err.Error()) os.Exit(1) } }
func (this *Processor) SaveOutputFiles(outputFolder string) error { logger.Print("\n Output Files:\n") // Save debug buffer filename := filepath.Join(outputFolder, "debug.log") err := logger.WriteBuffer(filename) if err != nil { return err } logger.Print(" => Debug buffer saved at %s", filename) // Save memory file filename = filepath.Join(outputFolder, "memory.dat") err = ioutil.WriteFile(filename, []byte(this.DataMemory().ToString()), 0644) if err != nil { return err } logger.Print(" => Data memory saved at %s", filename) // Save registers file filename = filepath.Join(outputFolder, "registers.dat") err = ioutil.WriteFile(filename, []byte(this.RegistersMemory().ToString()), 0644) if err != nil { return err } logger.Print(" => Registers memory saved at %s", filename) // Save pipeline flow filename = filepath.Join(outputFolder, "pipeline.dat") err = ioutil.WriteFile(filename, []byte(this.PipelineFlow()), 0644) if err != nil { return err } logger.Print(" => Pipeline flow saved at %s", filename) // Save stats filename = filepath.Join(outputFolder, "output.log") err = ioutil.WriteFile(filename, []byte(this.Config().ToString()+this.Stats()), 0644) if err != nil { return err } logger.Print(" => Output saved at %s", filename) return nil }
func (this *Dispatcher) Run(input channel.Channel, output map[info.CategoryEnum]channel.Channel, commonDataBus, recoveryBus channel.Channel) { // Create register alias table rat := registeraliastable.New(this.Index(), this.RegisterAliasTableEntries()) // Create re-order buffer rob := reorderbuffer.New(this.Index(), this.Processor(), this.StartOperationId(), this.ReorderBufferEntries(), this.InstructionsWrittenPerCycle(), rat) commonDataBusROB := channel.New(commonDataBus.Capacity()) // Create reservation station rs := reservationstation.New(this.Index(), this.Processor(), this.Registers(), this.ReservationStationEntries(), this.InstructionsFetchedPerCycle(), rat, rob.Bus()) commonDataBusRS := channel.New(commonDataBus.Capacity()) // Create storage bus this.dispatcher.bus = rob.Bus() // Launch each unit as a goroutine logger.Print(" => Initializing dispatcher unit %d", this.Index()) // Start dispatcher of operations to be executed into reservation station go this.runDispatcherToReservationStation(input, rs, rat, rob) // Start common bus multiplexer to send ack to reservation station and reorder buffer go this.runCommonBusMultiplexer(commonDataBus, commonDataBusRS, commonDataBusROB) // Run reservation station rs.Run(commonDataBusRS, output) // Run re-order buffer rob.Run(commonDataBusROB, recoveryBus) }
func (this *ReorderBuffer) Run(commonDataBus channel.Channel, recoveryBus channel.Channel) { // Launch unit as a goroutine logger.Print(" => Initializing re-order buffer unit %d", this.Index()) opId := this.StartOperationId() misprediction := false clockAllowed := this.Processor().Cycles() forceClose := false go func() { for { _, running := <-commonDataBus.Channel() if !running || misprediction { forceClose = true logger.Print(" => Flushing re-order buffer unit %d", this.Index()) return } commonDataBus.Release() } }() go func() { for { if forceClose { return } if this.Processor().Cycles() < clockAllowed { this.Processor().Wait(1) continue } // Commit in order, if missing an operation, wait for it computedAddress := uint32(0) robEntries := []RobEntry{} for robEntry, exists := this.Buffer()[opId]; exists; robEntry, exists = this.Buffer()[opId] { if uint32(len(robEntries)) >= this.InstructionsWrittenPerCycle() { break } // Ensure we can write results the next cycle result was written into ROB if this.Processor().Cycles() > robEntry.Cycle+1 { // Check for misprediction misprediction, computedAddress = this.checkForMisprediction(this.Buffer()[opId], robEntries) // Decrement speculative jumps this.Processor().DecrementSpeculativeJump() // Add to queue for commit robEntries = append(robEntries, robEntry) opId += 1 // If misprediction, do not process more rob entries if misprediction { break } } } this.commitRobEntries(robEntries) if misprediction { this.Processor().Wait(consts.WRITEBACK_CYCLES) recoveryBus.Add(operation.New(opId, computedAddress)) } clockAllowed = this.Processor().Cycles() + 1 } }() }
func (this *ReservationStation) runScheduler() { // For each operation received to schedule, process it for { value, running := <-this.Input().Channel() if !running { this.reservationStation.isActive = false logger.Print(" => Flushing reservation station unit %d (scheduler)", this.Index()) return } this.Lock() <- true op := operation.Cast(value) // Get next entry free entryIndex := this.getNextIndexFreeEntry() dest, valueOperands, memoryOperands := this.getComponentsFromInstruction(op.Instruction()) // Convert to operand objects ops := []Operand{} for _, register := range memoryOperands { ops = append(ops, newMemoryOp(register)) } for _, register := range valueOperands { ratEntry, ok := this.RegisterAliasTable().GetPhysicalRegister(op.Id()-1, uint32(register)) if ok { ops = append(ops, newRegisterRatOp(register, int32(ratEntry))) } else { ops = append(ops, newRegisterOp(register)) } } // Rat Dest regDestRat := newNilDep() if dest != INVALID_INDEX { if op.RenamedDestRegister() != INVALID_INDEX { regDestRat = newRegisterRatOp(dest, op.RenamedDestRegister()) } else { regDestRat = newRegisterOp(dest) } } dependencies := this.getDependencies(op.Id(), regDestRat, ops) logger.Collect(" => [RS%d][%03d]: Adding op to entry %d [D: %v, O's: %v, V's: %v] ..", this.Index(), op.Id(), entryIndex, regDestRat, ops, dependencies) // Store entry depency into reservation station this.Entries()[entryIndex] = RsEntry{ Operation: op, Destination: regDestRat, Operands: ops, Dependencies: dependencies, Free: false, Busy: false, } // If no waiting dependencies, release and execute if len(dependencies) == 0 { this.dispatchOperation(EntryIndex(entryIndex), op) } <-this.Lock() } }