diff --git a/.gitignore b/.gitignore index 393ca3d..50c73fa 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ coverage /cypress/videos/ /cypress/screenshots/ +DebuggerCmd.md # Editor directories and files .vscode/* diff --git a/server/src/Peripherals/DebuggerClient.cs b/server/src/Peripherals/DebuggerClient.cs new file mode 100644 index 0000000..ba14728 --- /dev/null +++ b/server/src/Peripherals/DebuggerClient.cs @@ -0,0 +1,252 @@ +using System.Net; +using DotNext; + +namespace Peripherals.DebuggerClient; + +/// +/// FPGA调试器的内存地址映射常量 +/// +class DebuggerAddr +{ + /// + /// 触发器启动地址 + /// + public const UInt32 Start = 0x5100_0000; + + /// + /// 刷新操作地址 + /// + public const UInt32 Fresh = 0x5100_FFFF; + + /// + /// 信号标志读取地址 + /// + public const UInt32 Signal = 0x5000_0001; + + /// + /// 数据读取基地址 + /// + public const UInt32 Data = 0x5100_0000; + + /// + /// 捕获模式设置地址 + /// + public const UInt32 Mode = 0x5101_0000; +} + +/// +/// FPGA调试器命令常量 +/// +class DebuggerCmd +{ + /// + /// 启动触发器命令 + /// + public const UInt32 Start = 0xFFFF_FFFF; + + /// + /// 刷新命令 + /// + public const UInt32 Fresh = 0x0000_0000; + + /// + /// 清除信号标志命令 + /// + public const UInt32 ClearSignal = 0xFFFF_FFFF; +} + +/// +/// 信号捕获模式枚举 +/// +public enum CaptureMode : byte +{ + /// + /// 无捕获模式 + /// + None = 0, + /// + /// 低电平触发模式 + /// + Logic0 = 1, + /// + /// 高电平触发模式 + /// + Logic1 = 2, + /// + /// 上升沿触发模式 + /// + Rise = 3, + /// + /// 下降沿触发模式 + /// + Fall = 4, +} + +/// +/// FPGA调试器客户端,用于通过UDP协议与FPGA调试器进行通信 +/// +public class DebuggerClient +{ + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + readonly int timeout = 2000; + readonly int taskID; + readonly int port; + readonly string address; + private IPEndPoint ep; + + private UInt32 captureDataAddr = 0x5100_0000; + + /// + /// 初始化FPGA调试器客户端 + /// + /// FPGA设备的IP地址 + /// 通信端口号 + /// 任务标识符 + /// 通信超时时间(毫秒),默认2000ms + /// 当timeout为负数时抛出 + public DebuggerClient(string address, int port, int taskID, int timeout = 2000) + { + if (timeout < 0) + throw new ArgumentException("Timeout couldn't be negative", nameof(timeout)); + this.address = address; + this.taskID = taskID; + this.port = port; + this.ep = new IPEndPoint(IPAddress.Parse(address), port); + this.timeout = timeout; + } + + /// + /// 设置信号捕获模式 + /// + /// 要设置的捕获模式 + /// 操作结果,成功返回true,失败返回错误信息 + public async ValueTask> SetMode(CaptureMode mode) + { + UInt32 data = ((UInt32)mode); + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Mode, data, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set mode: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to SetMode returned false"); + return new(new Exception("Failed to set mode")); + } + return true; + } + + /// + /// 启动信号触发器开始捕获 + /// + /// 操作结果,成功返回true,失败返回错误信息 + public async ValueTask> StartTrigger() + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Start, DebuggerCmd.Start, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to start trigger: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to StartTrigger returned false"); + return new(new Exception("Failed to start trigger")); + } + return true; + } + + /// + /// 读取触发器状态标志 + /// + /// 操作结果,成功返回状态标志字节,失败返回错误信息 + public async ValueTask> ReadFlag() + { + var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, DebuggerAddr.Signal, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read flag: {ret.Error}"); + return new(ret.Error); + } + if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 1) + { + logger.Error("ReadAddr returned invalid data for flag"); + return new(new Exception("Failed to read flag")); + } + return ret.Value.Options.Data[0]; + } + + /// + /// 清除触发器状态标志 + /// + /// 操作结果,成功返回true,失败返回错误信息 + public async ValueTask> ClearFlag() + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Signal, DebuggerCmd.ClearSignal, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to clear flag: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to ClearFlag returned false"); + return new(new Exception("Failed to clear flag")); + } + return true; + } + + /// + /// 从指定偏移地址读取捕获的数据 + /// + /// 数据读取的偏移地址 + /// 操作结果,成功返回32KB的捕获数据,失败返回错误信息 + public async ValueTask> ReadData(UInt16 offset) + { + var captureData = new byte[1024 * 32]; + { + var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset, 512, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read data: {ret.Error}"); + return new(ret.Error); + } + + Buffer.BlockCopy(ret.Value, 0, captureData, 0, 512 * 4); + } + { + var ret = await UDPClientPool.ReadAddr4BytesAsync(this.ep, this.taskID, this.captureDataAddr + offset + 512, 512, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read data: {ret.Error}"); + return new(ret.Error); + } + + Buffer.BlockCopy(ret.Value, 0, captureData, 512 * 4, 512 * 4); + } + + return captureData; + } + + /// + /// 刷新调试器状态,重置内部状态机 + /// + /// 操作结果,成功返回true,失败返回错误信息 + public async ValueTask> Refresh() + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, DebuggerAddr.Fresh, DebuggerCmd.Fresh, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to refresh: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to Refresh returned false"); + return new(new Exception("Failed to refresh")); + } + return true; + } +}