xous-core/emulation/peripherals/engine.cs
Sean Cross d248ab3cc7 emulation: engine: always update registers on write
These registers get reset when the hardware is reset, however the engine
may still be running. Ensure it's not running when the device is reset.

Signed-off-by: Sean Cross <sean@xobs.io>
2021-07-15 18:23:36 +08:00

652 lines
23 KiB
C#

//
// Copyright (c) 2010-2018 Antmicro
// Copyright (c) 2011-2015 Realtime Embedded
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Threading;
using System.Text;
using System.Linq;
using System.Globalization;
using System.Numerics;
using Antmicro.Renode.Core;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Peripherals.Bus;
namespace Antmicro.Renode.Peripherals.Miscellaneous
{
public class EngineRam : IDoubleWordPeripheral, IKnownSize
{
public EngineRam(Machine machine, Engine engine, long size)
{
this.machine = machine;
this.engine = engine;
this.size = size;
this.data = new UInt32[size / 4];
this.FIELD_MAX = BigInteger.Pow(2, 255) - 19;
this.UINT_MAX = BigInteger.Pow(2, 256) - 1;
this.ZERO = new BigInteger(0);
this.ONE = new BigInteger(1);
this.AM24 = new BigInteger(121665); // (A-2)/4
this.AP24 = new BigInteger(121666); // (A+2)/4
this.FIVE = new BigInteger(5);
this.TEN = new BigInteger(10);
this.TWENTY = new BigInteger(20);
this.FIFTY = new BigInteger(50);
this.ONE_HUNDRED = new BigInteger(100);
this.registers = new BigInteger[32];
for (var i = 0; i < this.registers.Length; i++)
{
this.registers[i] = this.ZERO;
}
}
public void SyncRegistersFromRam(ulong rf)
{
for (var i = 0; i < this.registers.Length; i++)
{
this.registers[i] = ReadRegisterFromRam(rf, (ulong)i);
}
}
public void SyncRegistersToRam(ulong rf)
{
for (var i = 0; i < this.registers.Length; i++)
{
WriteRegisterToRam(rf, (ulong)i, this.registers[i]);
}
}
public BigInteger UINT_MAX;
public BigInteger ZERO;
public BigInteger ONE;
public BigInteger AM24;
public BigInteger FIELD_MAX;
public BigInteger AP24;
public BigInteger FIVE;
public BigInteger TEN;
public BigInteger TWENTY;
public BigInteger FIFTY;
public BigInteger ONE_HUNDRED;
private Machine machine;
private Engine engine;
private long size;
public UInt32[] data;
public BigInteger[] registers;
public long Size { get { return size; } }
public void WriteDoubleWord(long address, uint value)
{
this.data[address / 4] = value;
}
public uint ReadDoubleWord(long offset)
{
return this.data[offset / 4];
}
public BigInteger ReadRegister(ulong register, bool constant)
{
// this.Log(LogLevel.Error, "Reading register {0} (const? {1}) from rf {2}", register, constant, rf);
if (constant)
{
switch (register)
{
case 0: return this.ZERO;
case 1: return this.ONE;
case 2: return this.AM24;
case 3: return this.FIELD_MAX;
case 4: return this.AP24;
case 5: return this.FIVE;
case 6: return this.TEN;
case 7: return this.TWENTY;
case 8: return this.FIFTY;
case 9: return this.ONE_HUNDRED;
default: return this.ZERO;
}
}
return this.registers[register];
}
public BigInteger ReadRegisterFromRam(ulong rf, ulong register)
{
var src_bytes = new byte[33]; // Keep leading byte 0 to force unsigned
// if ((rf >= 16) || (register >= 32))
// {
// throw new Exception(String.Format("Register #{0} or RegisterFile {1} is out of range", register, rf));
// }
for (ulong i = 0; i < 32; i += 4)
{
var offset = 0x10000 + (rf * 1024) + (register * 32) + i;
var bytes = BitConverter.GetBytes(this.data[offset / 4]);
if (bytes.Length != 4)
{
throw new Exception("Bytes was not equal to 4");
}
for (ulong j = 0; j < (ulong)bytes.Length; j++)
{
if ((i + j) >= 32)
{
throw new Exception("i+j>=32");
}
src_bytes[i + j] = bytes[j];
}
}
// this.Log(LogLevel.Error, "Returning read value from {0}-byte array", src_bytes.Length);
return new BigInteger(src_bytes);
}
public void WriteRegisterToRam(ulong rf, ulong register, BigInteger value)
{
// if ((rf >= 16) || (register >= 32))
// {
// throw new Exception(String.Format("Register #{0} or RegisterFile {1} is out of range", register, rf));
// }
var bytes = value.ToByteArray();
if (bytes.Length < 32)
{
bytes = bytes.Concat(new byte[32 - bytes.Length]);
}
var offset = 0x10000 + (rf * 1024) + (register * 32);
for (uint i = 0; i < 32; i += 4)
{
this.data[(offset + i) / 4] = (((uint)bytes[i + 0]) << 0)
| (((uint)bytes[i + 1]) << 8)
| (((uint)bytes[i + 2]) << 16)
| ((uint)(bytes[i + 3]) << 24);
}
}
public void WriteRegister(ulong register, BigInteger value)
{
this.registers[register] = value & this.UINT_MAX;
}
public String FormatValue(BigInteger value)
{
var trimmed_value = value & this.UINT_MAX;
var bytes = trimmed_value.ToByteArray();
if (bytes.Length < 32)
{
bytes = bytes.Concat(new byte[32 - bytes.Length]);
}
StringBuilder hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes.Reverse())
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
public void Reset()
{
for (var i = 0; i < this.data.Length; i++)
{
this.data[i] = 0;
}
}
}
public class Opcode : IFormattable
{
public Opcode(UInt32 op)
{
this.Op = (op >> 0) & 0x3f;
this.Ra = (op >> 6) & 0x1f;
this.Ca = ((op >> 11) & 1) == 1;
this.Rb = (op >> 12) & 0x1f;
this.Cb = ((op >> 17) & 1) == 1;
this.Wd = (op >> 18) & 0x1f;
this.Immediate = (int)(op >> 23);
if ((this.Immediate & (1 << 8)) != 0)
{
this.Immediate = this.Immediate - (1 << 9);
}
}
public String ConstantName(uint idx)
{
switch (idx)
{
case 0: return "zero";
case 1: return "one";
case 2: return "am24";
case 3: return "field";
case 4: return "ap24";
case 5: return "five";
case 6: return "ten";
case 7: return "twenty";
case 8: return "fifty";
case 9: return "one_hundred";
default: return "undef";
}
}
public String RaName()
{
if (this.Ca)
{
return "#" + this.ConstantName(this.Ra).ToUpper();
}
return "r" + this.Ra;
}
public String RbName()
{
if (this.Cb)
{
return "#" + this.ConstantName(this.Rb).ToUpper();
}
return "r" + this.Rb;
}
public String WdName()
{
return "r" + this.Wd;
}
public string Mnemonic()
{
switch (this.Op)
{
case 0: return "PSA";
case 1: return "PSB";
case 2: return "MSK";
case 3: return "XOR";
case 4: return "NOT";
case 5: return "ADD";
case 6: return "SUB";
case 7: return "MUL";
case 8: return "TRD";
case 9: return "BRZ";
case 10: return "FIN";
case 11: return "SHL";
case 12: return "XBT";
default: return "UDF";
}
}
public string ToString(string format, IFormatProvider provider)
{
if ((this.Op == 0) || (this.Op == 4) || (this.Op == 8) || (this.Op == 11) || (this.Op == 12))
{
return String.Format("{0} {1}, {2}", this.Mnemonic(), this.WdName(), this.RaName());
}
else if (this.Op == 1)
{
return String.Format("{0} {1}, {2}", this.Mnemonic(), this.WdName(), this.RbName());
}
else if (this.Op == 9)
{
return String.Format("{0} pc + {1}, {2}", this.Mnemonic(), this.Immediate, this.RaName());
}
else if (this.Op == 10)
{
return this.Mnemonic();
}
else if ((this.Op == 2) || (this.Op == 3) || (this.Op == 5) || (this.Op == 6) || (this.Op == 7)) // OK
{
return String.Format("{0} {1}, {2}, {3}", this.Mnemonic(), this.WdName(), this.RaName(), this.RbName());
}
else
{
return "undef";
}
}
public UInt32 Op;
public UInt32 Ra;
public bool Ca;
public UInt32 Rb;
public bool Cb;
public UInt32 Wd;
public Int32 Immediate;
}
public class Engine : IDoubleWordPeripheral, IProvidesRegisterCollection<DoubleWordRegisterCollection>, IKnownSize
{
public Engine(Machine machine, uint memAddr, uint memSize)
{
this.machine = machine;
this.engineRam = new EngineRam(machine, this, memSize);
machine.SystemBus.Register(this.engineRam, new BusRangeRegistration(memAddr, memSize));
this.RegistersCollection = new DoubleWordRegisterCollection(this);
this.IRQ = new GPIO();
Reset();
DefineRegisters();
}
~Engine()
{
if ((this.engineExecution != null) && (this.engineExecution.IsAlive))
{
this.engineExecution.Abort();
}
}
private void EngineThread()
{
// this.Log(LogLevel.Error, "ENGINE: Beginning execution. Current MPC: 0x{0:X}", this.mpstart);
try
{
// Interlocked.Exchange(ref this.mpc, this.mpstart);
this.mpc = this.mpstart;
var window = this.window;
this.engineRam.SyncRegistersFromRam(window);
while (true)
{
if (this.mpc >= this.mplen)
{
throw new Exception(String.Format("mpc exceeded program length of {0}", this.mplen));
}
var op = new Opcode(this.engineRam.data[this.mpc]);
// this.Log(LogLevel.Error, "RAM[0x{0:X}] {1}", this.mpc, op);
switch (op.Op)
{
case 0: // PSA
// this.engineRam.WriteRegister(op.Wd, this.engineRam.ReadRegister(op.Ra, op.Ca));
this.engineRam.WriteRegister(op.Wd, this.engineRam.ReadRegister(op.Ra, op.Ca));
break;
case 1: // PSB
this.engineRam.WriteRegister(op.Wd, this.engineRam.ReadRegister(op.Rb, op.Cb));
break;
case 2: // MSK
if ((this.engineRam.ReadRegister(op.Ra, op.Ca) & 1) != 0)
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.ReadRegister(op.Rb, op.Cb));
}
else
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.ZERO);
}
break;
case 3: // XOR
this.engineRam.WriteRegister(op.Wd,
(BigInteger.Pow(2, 256) | this.engineRam.ReadRegister(op.Ra, op.Ca)) ^ this.engineRam.ReadRegister(op.Rb, op.Cb));
break;
case 4: // NOT
this.engineRam.WriteRegister(op.Wd,
~(this.engineRam.ReadRegister(op.Ra, op.Ca) | BigInteger.Pow(2, 256)));
break;
case 5: // ADD
this.engineRam.WriteRegister(op.Wd,
this.engineRam.ReadRegister(op.Ra, op.Ca) + this.engineRam.ReadRegister(op.Rb, op.Cb));
break;
case 6: // SUB
{
var left = this.engineRam.ReadRegister(op.Ra, op.Ca);
var right = this.engineRam.ReadRegister(op.Rb, op.Cb);
var result = left - right;
// Wrap integer
if (result < 0)
{
result = BigInteger.Pow(2, 256) - result;
}
this.engineRam.WriteRegister(op.Wd, result);
}
break;
case 7: // MUL
this.engineRam.WriteRegister(op.Wd,
(this.engineRam.ReadRegister(op.Ra, op.Ca)
* this.engineRam.ReadRegister(op.Rb, op.Cb))
% this.engineRam.FIELD_MAX
);
break;
case 8: // TRD
if (this.engineRam.ReadRegister(op.Ra, op.Ca) >= this.engineRam.FIELD_MAX)
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.FIELD_MAX);
}
else
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.ZERO);
}
break;
case 9: // BRZ
if (this.engineRam.ReadRegister(op.Ra, op.Ca) == 0)
{
this.mpc = (ushort)((int)this.mpc + op.Immediate);
}
break;
case 10: // FIN
break;
case 11: // SHL
this.engineRam.WriteRegister(op.Wd, this.engineRam.ReadRegister(op.Ra, op.Ca) << 1);
break;
case 12: // XBT
if ((this.engineRam.ReadRegister(op.Ra, op.Ca) & (this.engineRam.ONE << 254)) != 0)
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.ONE);
}
else
{
this.engineRam.WriteRegister(op.Wd, this.engineRam.ZERO);
}
break;
default:
this.illegalOpcodeStatus = true;
throw new Exception("Unhandled opcode");
}
// Interlocked.Increment(ref this.mpc);
this.mpc += 1;
if (op.Op == 10)
{
break;
}
// this.Log(LogLevel.Error, " (Result) {0}", this.engineRam.FormatValue(this.engineRam.ReadRegister(op.Wd, false)));
}
}
catch (Exception e)
{
this.Log(LogLevel.Error, "ENGINE execution exception {0}", e);
}
this.finishedStatus = true;
// this.engineExecution = null;
this.engineRam.SyncRegistersToRam(window);
this.UpdateInterrupts();
this.finishedStatus = false;
this.illegalOpcodeStatus = false;
// this.Log(LogLevel.Error, "ENGINE finished execution");
}
private void DefineRegisters()
{
Registers.WINDOW.Define(this)
.WithValueField(0, 4, name: "WINDOW", valueProviderCallback: (_) => this.window, writeCallback: (_, value) => this.window = (byte)(value & 0xf))
;
Registers.MPSTART.Define(this)
.WithValueField(0, 11, name: "MPSTART", valueProviderCallback: (_) => this.mpstart, writeCallback: (_, value) => this.mpstart = (UInt16)(value & 0x7ff))
;
Registers.MPLEN.Define(this)
.WithValueField(0, 11, name: "MPLEN", valueProviderCallback: _ => this.mplen, writeCallback: (_, value) => this.mplen = (UInt16)(value & 0x7ff))
;
Registers.CONTROL.Define(this)
.WithFlag(0, FieldMode.Write, name: "GO", writeCallback: (_, value) =>
{
if (value)
{
if ((this.engineExecution != null) && (this.engineExecution.IsAlive))
{
this.Log(LogLevel.Error, "ENGINE already executing -- not re-running");
}
else
{
this.engineExecution = new Thread(this.EngineThread) { Name = "Engine Execution", IsBackground = true };
this.engineExecution.Start();
}
}
})
;
Registers.MPRESUME.Define(this)
.WithValueField(0, 11, name: "MPRESUME", valueProviderCallback: _ => this.mpresume, writeCallback: (_, value) => { this.mpresume = (UInt16)(value & 0x7ff); })
;
Registers.POWER.Define(this)
.WithFlag(0, out powerIsOn, FieldMode.Read | FieldMode.Write, name: "ON")
.WithFlag(1, out pauseRequest, FieldMode.Read | FieldMode.Write, name: "PAUSE_REQ")
;
Registers.STATUS.Define(this)
.WithFlag(0, name: "RUNNING", valueProviderCallback: (_) => this.engineExecution.IsAlive)
.WithValueField(1, 10, name: "MPC", valueProviderCallback: (_) => (uint)this.mpc)
.WithFlag(11, name: "PAUSE_GNT", valueProviderCallback: (_) => false)
;
Registers.EV_STATUS.Define32(this)
.WithFlag(0, FieldMode.Read, name: "FINISHED", valueProviderCallback: _ => finishedStatus)
.WithFlag(1, FieldMode.Read, name: "ILLEGAL_OPCODE", valueProviderCallback: _ => illegalOpcodeStatus)
;
Registers.EV_PENDING.Define32(this)
.WithFlag(0, out finishedPending, FieldMode.Read | FieldMode.WriteOneToClear, name: "FINISHED", changeCallback: (_, __) => UpdateInterrupts())
.WithFlag(1, out illegalOpcodePending, FieldMode.Read | FieldMode.WriteOneToClear, name: "ILLEGAL_OPCODE", changeCallback: (_, __) => UpdateInterrupts())
;
Registers.EV_ENABLE.Define32(this)
.WithFlag(0, out finishedEnabled, name: "FINISHED", changeCallback: (_, __) => UpdateInterrupts())
.WithFlag(1, out illegalOpcodeEnabled, name: "ILLEGAL_OPCODE", changeCallback: (_, __) => UpdateInterrupts())
;
Registers.INSTRUCTION.Define(this)
.WithValueField(0, 32, valueProviderCallback: _ => this.engineRam.data[this.mpc])
;
}
private void UpdateInterrupts()
{
if (this.illegalOpcodeStatus)
{
this.illegalOpcodePending.Value = true;
}
if (this.finishedStatus)
{
this.finishedPending.Value = true;
}
IRQ.Set((this.illegalOpcodePending.Value && this.illegalOpcodeEnabled.Value)
|| (this.finishedPending.Value && this.finishedEnabled.Value));
}
public void WriteDoubleWord(long address, uint value)
{
RegistersCollection.Write(address, value);
}
public uint ReadDoubleWord(long offset)
{
return RegistersCollection.Read(offset);
}
public long Size { get { return 4096; } }
public DoubleWordRegisterCollection RegistersCollection { get; private set; }
public void Reset()
{
this.window = 0;
this.mpstart = 0;
this.mplen = 0;
this.mpresume = 0;
this.mpc = 0;
if (this.engineExecution != null) {
if (this.engineExecution.IsAlive) {
this.engineExecution.Abort();
}
this.engineExecution = null;
}
if (this.powerIsOn != null)
{
this.powerIsOn.Value = false;
}
}
private readonly Machine machine;
public GPIO IRQ { get; private set; }
private byte window;
private UInt16 mpstart;
private UInt16 mplen;
private UInt16 mpresume;
private int mpc;
private IFlagRegisterField powerIsOn;
private IFlagRegisterField pauseRequest;
private IFlagRegisterField illegalOpcodeEnabled;
private IFlagRegisterField illegalOpcodePending;
private bool illegalOpcodeStatus;
private IFlagRegisterField finishedEnabled;
private IFlagRegisterField finishedPending;
private bool finishedStatus;
private Thread engineExecution;
private EngineRam engineRam;
private enum Registers
{
WINDOW = 0x00,
MPSTART = 0x04,
MPLEN = 0x08,
CONTROL = 0x0c,
MPRESUME = 0x10,
POWER = 0x14,
STATUS = 0x18,
EV_STATUS = 0x1c,
EV_PENDING = 0x20,
EV_ENABLE = 0x24,
INSTRUCTION = 0x28
}
}
public static class ExtensionMethods
{
public static byte[] Concat(this byte[] first, byte[] second)
{
var buffer = new byte[first.Length + second.Length];
Buffer.BlockCopy(first, 0, buffer, 0, first.Length);
Buffer.BlockCopy(second, 0, buffer, first.Length, second.Length);
return buffer;
}
public static byte[] Concat(this byte[] first, byte second)
{
var buffer = new byte[first.Length + 1];
Buffer.BlockCopy(first, 0, buffer, 0, first.Length);
buffer[buffer.Length - 1] = second;
return buffer;
}
}
}