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;
+ }
+}