AddCSLuaFile() DEFINE_BASECLASS( "base_wire_entity" ) ENT.PrintName = "Wire ZCPU" ENT.Author = "Black Phoenix" ENT.WireDebugName = "ZCPU" if CLIENT then return end -- No more client local cpu_max_frequency = 1400000 local wire_cpu_max_frequency = CreateConVar("wire_cpu_max_frequency", cpu_max_frequency, FCVAR_REPLICATED) cvars.AddChangeCallback("wire_cpu_max_frequency",function() cpu_max_frequency = math.Clamp(math.floor(wire_cpu_max_frequency:GetInt()),1,30000000) end) function ENT:Initialize() self:PhysicsInit(SOLID_VPHYSICS) self:SetMoveType(MOVETYPE_VPHYSICS) self:SetSolid(SOLID_VPHYSICS) self.Inputs = Wire_CreateInputs(self, { "MemBus", "IOBus", "Frequency", "Clk", "Reset", "Interrupt"}) self.Outputs = Wire_CreateOutputs(self, { "Error" }) -- CPU platform settings self.Clk = false -- whether the Clk input is on self.VMStopped = false -- whether the VM has halted itself (e.g. by running off the end of the program) self.Frequency = 2000 -- Create virtual machine self.VM = CPULib.VirtualMachine() self.VM.SerialNo = CPULib.GenerateSN("CPU") self.VM:Reset() self:SetCPUName() self:SetMemoryModel("64krom") self.VM.SignalError = function(VM,errorCode) Wire_TriggerOutput(self, "Error", errorCode) end self.VM.SignalShutdown = function(VM) self.VMStopped = true end self.VM.ExternalWrite = function(VM,Address,Value) if Address >= 0 then -- Use MemBus local MemBusSource = self.Inputs.MemBus.Src if MemBusSource then if MemBusSource.ReadCell then local result = MemBusSource:WriteCell(Address-self.VM.RAMSize,Value) if result then return true else VM:Interrupt(7,Address) return false end else VM:Interrupt(8,Address) return false end else VM:Interrupt(7,Address) return false end else -- Use IOBus local IOBusSource = self.Inputs.IOBus.Src if IOBusSource then if IOBusSource.ReadCell then local result = IOBusSource:WriteCell(-Address-1,Value) if result then return true else VM:Interrupt(10,-Address-1) return false end else VM:Interrupt(8,Address+1) return false end else return true end end end self.VM.ExternalRead = function(VM,Address) if Address >= 0 then -- Use MemBus local MemBusSource = self.Inputs.MemBus.Src if MemBusSource then if MemBusSource.ReadCell then local result = MemBusSource:ReadCell(Address-self.VM.RAMSize) if result then return result else VM:Interrupt(7,Address) return end else VM:Interrupt(8,Address) return end else VM:Interrupt(7,Address) return end else -- Use IOBus local IOBusSource = self.Inputs.IOBus.Src if IOBusSource then if IOBusSource.ReadCell then local result = IOBusSource:ReadCell(-Address-1) if result then return result else VM:Interrupt(10,-Address-1) return end else VM:Interrupt(8,Address+1) return end else return 0 end end end local oldReset = self.VM.Reset self.VM.Reset = function(...) if self.Clk and self.VMStopped then self:NextThink(CurTime()) end self.VMStopped = false return oldReset(...) end -- Player that debugs the processor self.DebuggerPlayer = nil end function ENT:ReadCell(Address) Address = math.floor(Address) return self.VM:ReadCell(Address) end function ENT:WriteCell(Address,Value) Address = math.floor(Address) return self.VM:WriteCell(Address,tonumber(Value) or 0) end local memoryModels = { ["8k"] = { 8192, 0 }, ["8krom"] = { 8192, 8192 }, ["32k"] = { 32768, 0 }, ["32krom"] = { 32768, 32768 }, ["64krom"] = { 65536, 65536 }, ["64k"] = { 65536, 0 }, ["128krom"] = { 131072, 131072 }, ["128rom"] = { 0, 128 }, ["128"] = { 128, 128 }, ["flat"] = { 0, 0 }, } function ENT:SetMemoryModel(model) self.VM.RAMSize = memoryModels[model][1] or 65536 self.VM.ROMSize = memoryModels[model][2] or 65536 end -- Execute ZCPU virtual machine function ENT:Run() -- Do not run if debugging is active if self.DebuggerPlayer then return end -- Calculate time-related variables local CurrentTime = CurTime() local DeltaTime = math.min(1/30,CurrentTime - (self.PreviousTime or 0)) self.PreviousTime = CurrentTime -- Check if need to run till specific instruction if self.BreakpointInstructions then self.VM.TimerDT = DeltaTime self.VM.CPUIF = self self.VM:Step(8,function(self) -- self:Emit("VM.IP = "..(self.PrecompileIP or 0)) -- self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) self:Dyn_Emit("if (VM.CPUIF.Clk and not VM.CPUIF.VMStopped) and (VM.CPUIF.OnVMStep) then") self:Dyn_EmitState() self:Emit("VM.CPUIF.OnVMStep()") self:Emit("end") self:Emit("if VM.CPUIF.BreakpointInstructions[VM.IP] then") self:Dyn_EmitState() self:Emit("VM.CPUIF.OnBreakpointInstruction(VM.IP)") self:Emit("VM.CPUIF.VMStopped = true") self:Emit("VM.TMR = VM.TMR + "..self.PrecompileInstruction) self:Emit("VM.CODEBYTES = VM.CODEBYTES + "..self.PrecompileBytes) self:Emit("if true then return end") self:Emit("end") self:Emit("if VM.CPUIF.LastInstruction and ((VM.IP > VM.CPUIF.LastInstruction) or VM.CPUIF.ForceLastInstruction) then") self:Dyn_EmitState() self:Emit("VM.CPUIF.ForceLastInstruction = nil") self:Emit("VM.CPUIF.OnLastInstruction()") self:Emit("VM.CPUIF.VMStopped = true") self:Emit("VM.TMR = VM.TMR + "..self.PrecompileInstruction) self:Emit("VM.CODEBYTES = VM.CODEBYTES + "..self.PrecompileBytes) self:Emit("if true then return end") self:Emit("end") end) self.VM.CPUIF = nil else -- How many steps VM must make to keep up with execution local Cycles = math.max(1,math.floor(self.Frequency*DeltaTime*0.5)) self.VM.TimerDT = (DeltaTime/Cycles) while (Cycles > 0) and (self.Clk) and (not self.VMStopped) and (self.VM.Idle == 0) do -- Run VM step local previousTMR = self.VM.TMR self.VM:Step() Cycles = Cycles - math.max(1, self.VM.TMR - previousTMR) end end -- Update VM timer self.VM.TIMER = self.VM.TIMER + DeltaTime -- Reset idle register self.VM.Idle = 0 end function ENT:Think() if (not game.SinglePlayer()) and (self.Frequency > cpu_max_frequency) then self.Frequency = cpu_max_frequency end self:Run() if self.Clk and not self.VMStopped then self:NextThink(CurTime()) end return true end -- Write data to RAM and then flash ROM if required function ENT:FlashData(data) self.VM:Reset() for k,v in pairs(data) do self.VM:WriteCell(k,tonumber(v) or 0) if (k >= 0) and (k < self.VM.ROMSize) then self.VM.ROM[k] = tonumber(v) or 0 end end end function ENT:SetCPUName(name) local overlayStr = "" local a = math.floor(self.VM.SerialNo / 100000) local b = math.floor(self.VM.SerialNo % 100000) if name and (name ~= "") then self:SetOverlayText(string.format("%s\nS/N %05d%05d",name,a,b)) else self:SetOverlayText(string.format("Zyelios CPU\nS/N %05d%05d",a,b)) end self.CPUName = name end function ENT:BuildDupeInfo() local info = BaseClass.BuildDupeInfo(self) or {} info.SerialNo = self.VM.SerialNo info.InternalRAMSize = self.VM.RAMSize info.InternalROMSize = self.VM.ROMSize info.CPUName = self.CPUName if self.VM.ROMSize > 0 then info.Memory = {} for k,v in pairs(self.VM.ROM) do if v ~= 0 then info.Memory[k] = v end end end return info end function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) self.VM.SerialNo = info.SerialNo or CPULib.GenerateSN("UNK") self.VM.RAMSize = info.InternalRAMSize or 65536 self.VM.ROMSize = info.InternalROMSize or 65536 self:SetCPUName(info.CPUName) if info.Memory then--and --(((info.UseROM) and (info.UseROM == true)) or -- ((info.InternalROMSize) and (info.InternalROMSize > 0))) then self.VM.ROM = {} for k,v in pairs(info.Memory) do self.VM.ROM[k] = tonumber(v) or 0 end self.VM:Reset() end end -- Compatibility with old NMI input WireLib.AddInputAlias( "NMI", "Interrupt" ) function ENT:TriggerInput(iname, value) if iname == "Clk" then self.Clk = (value >= 1) if self.Clk then self.VMStopped = false self:NextThink(CurTime()) end elseif iname == "Frequency" then if value > 0 then self.Frequency = math.floor(value) end elseif iname == "Reset" then --VM may be nil if self.VM.HWDEBUG ~= 0 then self.VM.DBGSTATE = math.floor(value) if (value > 0) and (value <= 1.0) then self.VM:Reset() end else if value >= 1.0 then self.VM:Reset() end end Wire_TriggerOutput(self, "Error", 0) elseif iname == "Interrupt" then if (value >= 32) && (value < 256) then if (self.Clk and not self.VMStopped) then self.VM:ExternalInterrupt(math.floor(value)) end end end end duplicator.RegisterEntityClass("gmod_wire_cpu", WireLib.MakeWireEnt, "Data")