diff --git a/Makefile b/Makefile index 356c1b2..45a123e 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,25 @@ SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum all: build .PHONY: build -build: build-cpusim +build: build-cpusim build-asmtest build-simtest .PHONY: build-cpusim build-cpusim: $(BINDIR)/cpusim $(BINDIR)/cpusim: $(SRC) - go build -trimpath -o $@ ./cmd/cpusim + go build -trimpath -o $@ ./cmd/$(@F) + +.PHONY: build-asmtest +build-asmtest: $(BINDIR)/asmtest + +$(BINDIR)/asmtest: $(SRC) + go build -trimpath -o $@ ./cmd/$(@F) + +.PHONY: build-simtest +build-simtest: $(BINDIR)/simtest + +$(BINDIR)/simtest: $(SRC) + go build -trimpath -o $@ ./cmd/$(@F) .PHONY: clean clean: diff --git a/cmd/asmtest/asmtest.go b/cmd/asmtest/asmtest.go new file mode 100644 index 0000000..f134669 --- /dev/null +++ b/cmd/asmtest/asmtest.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/cpu" + "git.elyanpoujol.fr/elyan/central-arch/pkg/instr" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" +) + +const ( + MOV_r1_r2 instr.Instr = 0xffff_ff2a + SVC_ffffff instr.Instr = 0xffff_ffff +) + +func main() { + cpu := cpu.New(simcontext.SimContext{}, nil) + cpu.RegisterInstr(instr.InstrDesc{ + Mnemonic: "LDR", + VariantName: "LDRIR", + OpCode: 0x01, + Format: instr.A, + Formatter: func(i *instr.DecodedInstr) string { return "Hello" }}) + + fmt.Printf("MOV[6:0] = %#0x\n", cpu.GetOpCode(MOV_r1_r2)) + fmt.Printf("SVC[6:0] = %#0x\n", cpu.GetOpCode(SVC_ffffff)) +} diff --git a/cmd/simtest/main.go b/cmd/simtest/main.go new file mode 100644 index 0000000..28c267b --- /dev/null +++ b/cmd/simtest/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "log" + "time" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/breakcfg" + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/instrset" + "git.elyanpoujol.fr/elyan/central-arch/pkg/sim" +) + +type SysoutEventLogger struct{} + +func (l *SysoutEventLogger) Log(e events.SimEvent) { + log.Printf("%v\n", e) +} + +func main() { + eventLogger := &SysoutEventLogger{} + + simConfig := sim.SimConfig{ + RamSize: 64, + UseMMU: false, + UseCache: false, + } + + schedulerConfig := sim.SchedulerConfig{BreakableEventConfig: breakcfg.New()} + breakcfg.SetSteppingType[events.CpuDecodedEvent](schedulerConfig.BreakableEventConfig, breakcfg.ALLOW_BREAK) + + sim := sim.New(simConfig, schedulerConfig, eventLogger) + + sim.RegisterInstructionSet(instrset.CentralInstructionSet) + + /* + sim.QueueStateAccess(func(s simstate.SimState) { + s.RamState.Data[0] = 0xdeaddead + }) + */ + + go func() { + sim.Start() + }() + + time.Sleep(5 * time.Second) + sim.Stop() + time.Sleep(time.Second) +} diff --git a/go.mod b/go.mod index 5673db3..7dc8121 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.elyanpoujol.fr/elyan/cpu +module git.elyanpoujol.fr/elyan/central-arch go 1.23.2 diff --git a/pkg/breakcfg/breakcfg.go b/pkg/breakcfg/breakcfg.go new file mode 100644 index 0000000..4740f9c --- /dev/null +++ b/pkg/breakcfg/breakcfg.go @@ -0,0 +1,91 @@ +package breakcfg + +import ( + "reflect" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" +) + +type BreakableEventId uint + +/* +const ( + // CPU events + CPU_FETCH_INSTR_BEGIN BreakableEventId = iota + CPU_FETCH_INSTR_END + CPU_DECODED_INSTR + CPU_EXECUTED_INSTR + CPU_READ_MEM_REQUEST + CPU_WRITE_MEM_REQUEST + CPU_MODE_TRANSITION + // MMU events + MMU_TLB_NEXT_ENTRY + MMU_TLB_HIT + MMU_TLB_MISS + MMU_TLB_EVICT_ENTRY + MMU_TLB_NEW_ENTRY + MMU_WALK_NEXT_ENTRY + MMU_WALK_NEXT_LEVEL + MMU_WALK_HIT + MMU_WALK_MISS + // Cache events + CACHE_STEP_NEXT_ENTRY + CACHE_STEP_HIT + CACHE_STEP_MISS + CACHE_EVICT_ENTRY + CACHE_NEW_ENTRY + // Memory events + MEMORY_READ + MEMORY_WRITE + MEMORY_UNALIGNED_READ + MEMORY_UNALIGNED_WRITE + // Event count, not a real event + BREAKABLE_EVENT_ID_COUNT +) +*/ + +type SteppingType uint8 + +const ( + ALLOW_BREAK SteppingType = iota // Default value, allow breakpoints for a given event type + ALWAYS_BREAK // Always break even if no breakpoints were set for the event type + IGNORE_BREAK // Ignore the breakpoints for the event type +) + +type BreakableEventConfig struct { + eventSteppingTypes map[reflect.Type]SteppingType +} + +func New() *BreakableEventConfig { + cfg := &BreakableEventConfig{ + eventSteppingTypes: make(map[reflect.Type]SteppingType), + } + cfg.init() + + return cfg +} + +func SetSteppingType[T events.SimEvent](cfg *BreakableEventConfig, st SteppingType) { + cfg.eventSteppingTypes[key[T]()] = st +} + +func GetSteppingType[T events.SimEvent](cfg *BreakableEventConfig) SteppingType { + return cfg.eventSteppingTypes[key[T]()] +} + +func (cfg *BreakableEventConfig) init() { + // TODO Add all breakable events + SetSteppingType[events.CpuFetchBeginEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuFetchEndEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuDecodedEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuExecuteEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuReadMemoryBeginEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuReadMemoryEndEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuWriteMemoryBeginEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuWriteMemoryEndEvent](cfg, ALLOW_BREAK) + SetSteppingType[events.CpuModeTransitionEvent](cfg, ALLOW_BREAK) +} + +func key[T events.SimEvent]() reflect.Type { + return reflect.TypeFor[T]() +} diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go new file mode 100644 index 0000000..67e2dab --- /dev/null +++ b/pkg/cache/cache.go @@ -0,0 +1,25 @@ +package cache + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/memory" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" +) + +// TODO Add a field of type CacheState +type Cache struct{} + +func (c *Cache) Read(addr uint32) (uint32, error) { + panic("Not implemented yet") +} + +func (c *Cache) Write(addr uint32, value uint32) error { + panic("Not implemented yet") +} + +func (m *Cache) Fetch(addr uint32) (uint32, error) { + panic("Not implemented yet") +} + +func New(context simcontext.SimContext, realMemory memory.Memory) *Cache { + panic("Not implemented yet") +} diff --git a/pkg/cpu/cpu.go b/pkg/cpu/cpu.go new file mode 100644 index 0000000..b80d93f --- /dev/null +++ b/pkg/cpu/cpu.go @@ -0,0 +1,28 @@ +package cpu + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/memory" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstate" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstep" +) + +type Cpu struct { + state simstate.CpuState + stepper simstep.SimStepper + eventLogger events.EventLogger + memory memory.Memory +} + +func New(context simcontext.SimContext, memory memory.Memory) *Cpu { + return &Cpu{ + stepper: context.Stepper, + eventLogger: context.EventLogger, + memory: memory, + } +} + +func (c *Cpu) State() *simstate.CpuState { + return &c.state +} diff --git a/pkg/cpu/exec.go b/pkg/cpu/exec.go new file mode 100644 index 0000000..02bd3cf --- /dev/null +++ b/pkg/cpu/exec.go @@ -0,0 +1,33 @@ +package cpu + +import ( + "sync" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstep" +) + +func (cpu *Cpu) Run(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + // TODO Implement CPU + + var err error + + for err == nil { + event := &events.DebugEvent{Msg: "Will step"} + + cpu.eventLogger.Log(event) + err = cpu.stepper.RequestStep(event) + + if err != nil { + if err != simstep.SimQuitRequest { + panic(err) + } + + cpu.eventLogger.Log(&events.DebugEvent{Msg: "Scheduler quit requested"}) + } else { + cpu.eventLogger.Log(&events.DebugEvent{Msg: "After step"}) + } + } +} diff --git a/pkg/cpu/instr.go b/pkg/cpu/instr.go new file mode 100644 index 0000000..52a13fb --- /dev/null +++ b/pkg/cpu/instr.go @@ -0,0 +1,26 @@ +package cpu + +import ( + "log" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/instr" +) + +const instrMask = 0x7f + +func (cpu *Cpu) RegisterInstr(instrDesc instr.InstrDesc) { + // TODO Implement it + log.Printf("Registered: %+v\n", instrDesc) +} + +func (cpu *Cpu) GetOpCode(i instr.Instr) uint32 { + return uint32(i & instrMask) +} + +func (cpu *Cpu) GetFormat(i instr.Instr) instr.InstrFormat { + panic("Not implemented yet") +} + +func (cpu *Cpu) Decode(i instr.Instr, decodedInstr *instr.DecodedInstr) { + panic("Not implemented yet") +} diff --git a/pkg/events/events.go b/pkg/events/events.go new file mode 100644 index 0000000..fce2859 --- /dev/null +++ b/pkg/events/events.go @@ -0,0 +1,66 @@ +package events + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/modes" +) + +type SimEvent interface{} + +type DebugEvent struct { + Msg string +} + +func (de *DebugEvent) String() string { + return de.Msg +} + +type CpuFetchBeginEvent struct { + Addr uint32 +} + +type CpuFetchEndEvent struct { + Addr uint32 + Value uint32 +} + +type CpuDecodedEvent struct { + Addr uint32 + InstrValue uint32 + InstrString string +} + +type CpuExecuteEvent struct { + Addr uint32 + InstrValue uint32 + InstrString string +} + +type CpuReadMemoryBeginEvent struct { + Addr uint32 +} + +type CpuReadMemoryEndEvent struct { + Addr uint32 + Value uint32 +} + +type CpuWriteMemoryBeginEvent struct { + Addr uint32 + Value uint32 +} + +type CpuWriteMemoryEndEvent struct { + Addr uint32 + Value uint32 +} + +type CpuModeTransitionEvent struct { + Prev modes.CpuMode + Next modes.CpuMode +} + +// TODO Add more events + +type EventLogger interface { + Log(e SimEvent) +} diff --git a/pkg/instr/instr.go b/pkg/instr/instr.go new file mode 100644 index 0000000..58028d7 --- /dev/null +++ b/pkg/instr/instr.go @@ -0,0 +1,101 @@ +package instr + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/register" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstate" +) + +type Instr uint32 +type InstrFormat uint32 + +const ( + A InstrFormat = iota + B + C + D1 + D2 + E +) + +func (instrf InstrFormat) String() string { + switch instrf { + case A: + return "A" + + case B: + return "B" + + case C: + return "C" + + case D1: + return "D1" + + case D2: + return "D2" + + case E: + return "E" + } + + panic("unknown format") +} + +type Cond uint32 + +const ( + AL Cond = iota // Always + EQ // Equal, Z==1 + NEQ // Not Equal, Z==0 + UGE // Unsigned Greater or Equal, C==1 + CS Cond = iota - 1 // Carry Set, C==1 + ULT // Unsigned Lower Than, C==0 + CC Cond = iota - 2 // Carry Clear, C==0 + NEG // Negative, N==1 + POS // Positive, N==0 + VS // oVerflow Set, V==1 + VC // oVerflow Clear, V==0 + UGT // Unsigned Greater Than, C==1 && Z==0 + ULE // Unsigned Lower Than, C==0 && Z==1 + SGE // Signed Greater or Equal, N==V + SLT // Signed Lower Than, N!=V + SGT // Signed Greater Than, Z==0 && N==V + SLE // Signed Lower or Equal, Z==1 && N!=V +) + +type InstrFormatter func(*DecodedInstr) string + +type InstrBehavior func(*DecodedInstr, simstate.SimState) error + +type InstrDesc struct { + VariantName string + Mnemonic string + OpCode uint32 + Format InstrFormat + Formatter InstrFormatter + Behavior InstrBehavior +} + +type StandardFormatFields struct { + Condition Cond + Rd register.CpuRegister + Rs1 register.CpuRegister + Rs2 register.CpuRegister + Imm uint32 +} + +type SubsystemFormatFields struct { + Reg register.CpuRegister + Sid uint32 + Sre uint32 + Cmd uint32 +} + +type DecodedInstr struct { + Format InstrFormat + OpCode uint32 + // A, B, C, D1 and D2 formats fields + StandardFormatFields + // E format fields + SubsystemFormatFields +} diff --git a/pkg/instrset/Instrset.go b/pkg/instrset/Instrset.go new file mode 100644 index 0000000..d7e1de1 --- /dev/null +++ b/pkg/instrset/Instrset.go @@ -0,0 +1,14 @@ +package instrset + +import "git.elyanpoujol.fr/elyan/central-arch/pkg/instr" + +var CentralInstructionSet = []instr.InstrDesc{ + {VariantName: "NOP", Mnemonic: "nop", OpCode: 0x0, Format: instr.D2, Formatter: nil, Behavior: nil}, + {VariantName: "LDRIR", Mnemonic: "ldr", OpCode: 0x1, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "LDRIRW", Mnemonic: "ldr", OpCode: 0x2, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "LDRIOW", Mnemonic: "ldr", OpCode: 0x3, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "LDRR", Mnemonic: "ldr", OpCode: 0x4, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "LDRRW", Mnemonic: "ldr", OpCode: 0x5, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "LDROW", Mnemonic: "ldr", OpCode: 0x6, Format: instr.A, Formatter: nil, Behavior: nil}, + {VariantName: "SVC", Mnemonic: "svc", OpCode: 0x7f, Format: instr.D2, Formatter: nil, Behavior: nil}, +} diff --git a/pkg/memory/memory.go b/pkg/memory/memory.go new file mode 100644 index 0000000..c9a5cf4 --- /dev/null +++ b/pkg/memory/memory.go @@ -0,0 +1,7 @@ +package memory + +type Memory interface { + Read(addr uint32) (uint32, error) + Write(addr uint32, value uint32) error + Fetch(addr uint32) (uint32, error) +} diff --git a/pkg/mmu/mmu.go b/pkg/mmu/mmu.go new file mode 100644 index 0000000..33323ba --- /dev/null +++ b/pkg/mmu/mmu.go @@ -0,0 +1,25 @@ +package mmu + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/memory" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" +) + +// TODO Add a field of type MmuState +type MMU struct{} + +func (m *MMU) Read(addr uint32) (uint32, error) { + panic("Not implemented yet") +} + +func (m *MMU) Write(addr uint32, value uint32) error { + panic("Not implemented yet") +} + +func (m *MMU) Fetch(addr uint32) (uint32, error) { + panic("Not implemented yet") +} + +func New(context simcontext.SimContext, realMemory memory.Memory) *MMU { + panic("Not implemented yet") +} diff --git a/pkg/modes/modes.go b/pkg/modes/modes.go new file mode 100644 index 0000000..964d484 --- /dev/null +++ b/pkg/modes/modes.go @@ -0,0 +1,16 @@ +package modes + +type CpuMode uint + +const ( + USER CpuMode = iota + SUPERVISOR + FAULT +) + +type CpuPrivilegeLevel uint + +const ( + UNPRIVILEGED CpuPrivilegeLevel = iota + PRIVILEGED +) diff --git a/pkg/ram/ram.go b/pkg/ram/ram.go new file mode 100644 index 0000000..c571d34 --- /dev/null +++ b/pkg/ram/ram.go @@ -0,0 +1,52 @@ +package ram + +import ( + "errors" + "fmt" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstate" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstep" +) + +type RAM struct { + state simstate.RamState + stepper simstep.SimStepper + eventLogger events.EventLogger +} + +const alignment = 4 + +func New(context simcontext.SimContext, bytes uint) (*RAM, error) { + if bytes < alignment { + return nil, errors.New("memory size should be at least 4 bytes") + } + + return &RAM{ + state: simstate.RamState{Data: make([]uint32, bytes/alignment)}, + stepper: context.Stepper, + eventLogger: context.EventLogger, + }, nil +} + +func (r *RAM) Read(addr uint32) (uint32, error) { + if addr%alignment != 0 { + return 0, fmt.Errorf("unaligned memory address: %08x", addr) + } + + return r.state.Data[addr/alignment], nil +} + +func (r *RAM) Write(addr uint32, value uint32) error { + if addr%alignment != 0 { + return fmt.Errorf("unaligned memory address: %08x", addr) + } + + r.state.Data[addr/alignment] = value + return nil +} + +func (m *RAM) Fetch(addr uint32) (uint32, error) { + return m.Read(addr) +} diff --git a/pkg/register/register.go b/pkg/register/register.go new file mode 100644 index 0000000..f80ed88 --- /dev/null +++ b/pkg/register/register.go @@ -0,0 +1,33 @@ +package register + +type CpuRegister uint32 +type CpuStatusRegister uint32 + +const ( + R0 CpuRegister = iota + R1 + R2 + R3 + R4 + R5 + R6 + R7 + LR CpuRegister = iota + 15 + PC + SP + PC_USER + SP_USER + PC_SYS + SP_SYS + PC_FAULT + SP_FAULT +) + +// General purpose registers count +const GPR_COUNT = 8 + +const ( + PSR CpuStatusRegister = iota + ESR_SVC + ESR_FAULT +) diff --git a/pkg/sim/breakpoint.go b/pkg/sim/breakpoint.go new file mode 100644 index 0000000..868729a --- /dev/null +++ b/pkg/sim/breakpoint.go @@ -0,0 +1,30 @@ +package sim + +type CpuBreakpoint struct { + Addr uint32 +} + +type WatchpointType uint + +const ( + READ WatchpointType = iota + WRITE + READ_WRITE +) + +type CpuWatchpoint struct { + Addr uint32 + Type WatchpointType +} + +type CpuModeTransitionBreakType uint + +const ( + NONE CpuModeTransitionBreakType = iota + ALL + USER + SUPERVISOR + FAULT + PRIVILEGED + UNPRIVILEGED +) diff --git a/pkg/sim/commands.go b/pkg/sim/commands.go new file mode 100644 index 0000000..4a75b93 --- /dev/null +++ b/pkg/sim/commands.go @@ -0,0 +1,3 @@ +package sim + +type Command interface{} diff --git a/pkg/sim/scheduler.go b/pkg/sim/scheduler.go new file mode 100644 index 0000000..17cd679 --- /dev/null +++ b/pkg/sim/scheduler.go @@ -0,0 +1,102 @@ +package sim + +import ( + "log" + "sync" + "time" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/breakcfg" + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstep" +) + +type SchedulerConfig struct { + BreakableEventConfig *breakcfg.BreakableEventConfig +} + +type Scheduler struct { + config SchedulerConfig + cpuBreakpoints map[CpuBreakpoint]bool + cpuWatchpoints map[CpuWatchpoint]bool + cpuModeTransitionBreakType CpuModeTransitionBreakType + stepRequest chan events.SimEvent + stepResponse chan bool + commandRequest chan Command + commandReponse chan bool + breakEvent chan *Sim + quitEvent <-chan bool +} + +func NewScheduler(config SchedulerConfig, quitEvent <-chan bool) *Scheduler { + return &Scheduler{ + config: config, + cpuBreakpoints: make(map[CpuBreakpoint]bool), + cpuWatchpoints: make(map[CpuWatchpoint]bool), + // When the scheduler stopped running, we don't want the client to block + // when sending to this channel + stepRequest: make(chan events.SimEvent, 1), + stepResponse: make(chan bool), + // When the scheduler stopped running, we don't want the client to block + // when sending to this channel + commandRequest: make(chan Command, 1), + commandReponse: make(chan bool), + breakEvent: make(chan *Sim), + quitEvent: quitEvent, + } +} + +func (s *Scheduler) Run(wg *sync.WaitGroup) { + wg.Add(1) + defer wg.Done() + + for { + select { + case <-s.quitEvent: + s.closeSendingChannels() + return + + case req := <-s.stepRequest: + s.handleStepRequest(req) + + case cmd := <-s.commandRequest: + s.handleSchedulingCommand(cmd) + } + } +} + +func (s *Scheduler) closeSendingChannels() { + close(s.stepResponse) + close(s.commandReponse) + close(s.breakEvent) +} + +func (s *Scheduler) Cleanup() { + close(s.stepRequest) + close(s.commandRequest) +} + +func (s *Scheduler) handleStepRequest(req simstep.SimStepRequest) { + // TODO Handle request + // TODO Check breakpoints + log.Println("step !") + time.Sleep(time.Second) + s.stepResponse <- true +} + +func (s *Scheduler) handleSchedulingCommand(cmd Command) { + // TODO Handle scheduling command +} + +func (s *Scheduler) RequestStep(req simstep.SimStepRequest) error { + s.stepRequest <- req + + // The scheduler always sends true, to receive false means that the channel is closed + // and thus, that we must quit + doContinue := <-s.stepResponse + + if !doContinue { + return simstep.SimQuitRequest + } + + return nil +} diff --git a/pkg/sim/sim.go b/pkg/sim/sim.go new file mode 100644 index 0000000..acee490 --- /dev/null +++ b/pkg/sim/sim.go @@ -0,0 +1,156 @@ +package sim + +import ( + "sync" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/cache" + "git.elyanpoujol.fr/elyan/central-arch/pkg/cpu" + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/instr" + "git.elyanpoujol.fr/elyan/central-arch/pkg/memory" + "git.elyanpoujol.fr/elyan/central-arch/pkg/mmu" + "git.elyanpoujol.fr/elyan/central-arch/pkg/ram" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simcontext" +) + +type SimStatus struct { + sync.Mutex + Started bool +} + +type Sim struct { + status SimStatus + config SimConfig + cpu *cpu.Cpu + ram *ram.RAM + cache *cache.Cache + mmu *mmu.MMU + scheduler *Scheduler + simQuitEvent chan bool + schedulerQuitEvent chan<- bool +} + +type SimConfig struct { + RamSize uint + UseMMU bool + UseCache bool +} + +func New(simConfig SimConfig, schedulerConfig SchedulerConfig, eventLogger events.EventLogger) *Sim { + var cpuMemory memory.Memory + var mmuMemory *mmu.MMU + var cacheMemory *cache.Cache + + // We make the channel buffered to not cause wait for the scheduler to read the quit event + // as we also wait on the wait group + schedulerQuitEvent := make(chan bool, 1) + simScheduler := NewScheduler(schedulerConfig, schedulerQuitEvent) + + context := simcontext.SimContext{EventLogger: eventLogger, Stepper: simScheduler} + + ram, err := ram.New(context, simConfig.RamSize) + // By default, the CPU memory is the RAM + cpuMemory = ram + + if simConfig.UseMMU && !simConfig.UseCache { + // Wrap the RAM behind MMU + mmuMemory = mmu.New(context, ram) + cpuMemory = mmuMemory + } else if !simConfig.UseMMU && simConfig.UseCache { + // Wrap the RAM behind Cache + cacheMemory = cache.New(context, ram) + cpuMemory = cacheMemory + } else if simConfig.UseMMU && simConfig.UseCache { + // Wrap the RAM behind Cache and wrap Cache behind MMU + cacheMemory = cache.New(context, ram) + mmuMemory = mmu.New(context, cacheMemory) + cpuMemory = mmuMemory + } + + cpu := cpu.New(context, cpuMemory) + + if err != nil { + panic(err) + } + + return &Sim{ + config: simConfig, + cpu: cpu, + ram: ram, + cache: cacheMemory, + mmu: mmuMemory, + scheduler: simScheduler, + simQuitEvent: make(chan bool), + schedulerQuitEvent: schedulerQuitEvent, + } +} + +func (s *Sim) Start() { + s.setStarted(true) + defer s.setStarted(false) + + wg := &sync.WaitGroup{} + + go s.cpu.Run(wg) + go s.scheduler.Run(wg) + + // Wait for quit event + <-s.simQuitEvent + // Signal the scheduler to quit + s.schedulerQuitEvent <- false + + wg.Wait() + + // Do some cleanup + s.scheduler.Cleanup() +} + +func (s *Sim) Stop() { + if s.started() { + s.simQuitEvent <- false + } +} + +func (s *Sim) RegisterInstructionSet(instrSet []instr.InstrDesc) { + for _, instr := range instrSet { + s.cpu.RegisterInstr(instr) + } +} + +func (s *Sim) Cpu() *cpu.Cpu { + return s.cpu +} + +func (s *Sim) Ram() *ram.RAM { + return s.ram +} + +func (s *Sim) UseMMU() bool { + return s.config.UseMMU +} + +func (s *Sim) MMU() *mmu.MMU { + return s.mmu +} + +func (s *Sim) UseCache() bool { + return s.config.UseCache +} + +func (s *Sim) Cache() *cache.Cache { + return s.cache +} + +func (s *Sim) setStarted(started bool) { + s.status.Lock() + defer s.status.Unlock() + + s.status.Started = started +} + +func (s *Sim) started() bool { + s.status.Lock() + defer s.status.Unlock() + + return s.status.Started +} diff --git a/pkg/simcontext/context.go b/pkg/simcontext/context.go new file mode 100644 index 0000000..411219a --- /dev/null +++ b/pkg/simcontext/context.go @@ -0,0 +1,11 @@ +package simcontext + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" + "git.elyanpoujol.fr/elyan/central-arch/pkg/simstep" +) + +type SimContext struct { + EventLogger events.EventLogger + Stepper simstep.SimStepper +} diff --git a/pkg/simstate/state.go b/pkg/simstate/state.go new file mode 100644 index 0000000..a742818 --- /dev/null +++ b/pkg/simstate/state.go @@ -0,0 +1,44 @@ +package simstate + +import ( + "git.elyanpoujol.fr/elyan/central-arch/pkg/register" +) + +type CpuRegisters struct { + Gprs [register.GPR_COUNT]uint32 + Lr uint32 + PcUser uint32 + SpUser uint32 + PcSvc uint32 + SpSvc uint32 + PcFault uint32 + SpFault uint32 +} + +type CpuStatusRegisters struct { + Psr uint32 + EsrSvc uint32 + EsrFault uint32 +} + +type CpuState struct { + Registers CpuRegisters + StatusRegisters CpuStatusRegisters +} + +type RamState struct { + Data []uint32 +} + +// TODO Implement it +type CacheState struct{} + +// TODO Implement it +type MmuState struct{} + +type SimState struct { + CpuState *CpuState + RamState *RamState + CacheState *CacheState + MmuState *MmuState +} diff --git a/pkg/simstep/stepper.go b/pkg/simstep/stepper.go new file mode 100644 index 0000000..d1ff317 --- /dev/null +++ b/pkg/simstep/stepper.go @@ -0,0 +1,17 @@ +package simstep + +import ( + "errors" + + "git.elyanpoujol.fr/elyan/central-arch/pkg/events" +) + +var SimQuitRequest = errors.New("simulation quit requested") + +type SimStepRequest interface { + events.SimEvent +} + +type SimStepper interface { + RequestStep(req SimStepRequest) error +} diff --git a/pkg/subsystem/subsystem.go b/pkg/subsystem/subsystem.go new file mode 100644 index 0000000..6620dd5 --- /dev/null +++ b/pkg/subsystem/subsystem.go @@ -0,0 +1,6 @@ +package subsystem + +type Subsystem interface { + Read(register uint8, cmd uint8) (uint32, error) + Write(register uint8, cmd uint8, value uint32) error +}