Compare commits
2 Commits
519094b3a0
...
43e3cce048
Author | SHA1 | Date |
---|---|---|
|
43e3cce048 | |
|
bcdefb2779 |
|
@ -18,6 +18,7 @@ coverage
|
|||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
DebuggerCmd.md
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
using System.Net;
|
||||
using DotNext;
|
||||
|
||||
namespace Peripherals.DebuggerClient;
|
||||
|
||||
/// <summary>
|
||||
/// FPGA调试器的内存地址映射常量
|
||||
/// </summary>
|
||||
class DebuggerAddr
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发器启动地址
|
||||
/// </summary>
|
||||
public const UInt32 Start = 0x5100_0000;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新操作地址
|
||||
/// </summary>
|
||||
public const UInt32 Fresh = 0x5100_FFFF;
|
||||
|
||||
/// <summary>
|
||||
/// 信号标志读取地址
|
||||
/// </summary>
|
||||
public const UInt32 Signal = 0x5000_0001;
|
||||
|
||||
/// <summary>
|
||||
/// 数据读取基地址
|
||||
/// </summary>
|
||||
public const UInt32 Data = 0x5100_0000;
|
||||
|
||||
/// <summary>
|
||||
/// 捕获模式设置地址
|
||||
/// </summary>
|
||||
public const UInt32 Mode = 0x5101_0000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FPGA调试器命令常量
|
||||
/// </summary>
|
||||
class DebuggerCmd
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动触发器命令
|
||||
/// </summary>
|
||||
public const UInt32 Start = 0xFFFF_FFFF;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新命令
|
||||
/// </summary>
|
||||
public const UInt32 Fresh = 0x0000_0000;
|
||||
|
||||
/// <summary>
|
||||
/// 清除信号标志命令
|
||||
/// </summary>
|
||||
public const UInt32 ClearSignal = 0xFFFF_FFFF;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 信号捕获模式枚举
|
||||
/// </summary>
|
||||
public enum CaptureMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 无捕获模式
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// 低电平触发模式
|
||||
/// </summary>
|
||||
Logic0 = 1,
|
||||
/// <summary>
|
||||
/// 高电平触发模式
|
||||
/// </summary>
|
||||
Logic1 = 2,
|
||||
/// <summary>
|
||||
/// 上升沿触发模式
|
||||
/// </summary>
|
||||
Rise = 3,
|
||||
/// <summary>
|
||||
/// 下降沿触发模式
|
||||
/// </summary>
|
||||
Fall = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FPGA调试器客户端,用于通过UDP协议与FPGA调试器进行通信
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FPGA调试器客户端
|
||||
/// </summary>
|
||||
/// <param name="address">FPGA设备的IP地址</param>
|
||||
/// <param name="port">通信端口号</param>
|
||||
/// <param name="taskID">任务标识符</param>
|
||||
/// <param name="timeout">通信超时时间(毫秒),默认2000ms</param>
|
||||
/// <exception cref="ArgumentException">当timeout为负数时抛出</exception>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置信号捕获模式
|
||||
/// </summary>
|
||||
/// <param name="mode">要设置的捕获模式</param>
|
||||
/// <returns>操作结果,成功返回true,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动信号触发器开始捕获
|
||||
/// </summary>
|
||||
/// <returns>操作结果,成功返回true,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取触发器状态标志
|
||||
/// </summary>
|
||||
/// <returns>操作结果,成功返回状态标志字节,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<byte>> 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];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除触发器状态标志
|
||||
/// </summary>
|
||||
/// <returns>操作结果,成功返回true,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定偏移地址读取捕获的数据
|
||||
/// </summary>
|
||||
/// <param name="offset">数据读取的偏移地址</param>
|
||||
/// <returns>操作结果,成功返回32KB的捕获数据,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<byte[]>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新调试器状态,重置内部状态机
|
||||
/// </summary>
|
||||
/// <returns>操作结果,成功返回true,失败返回错误信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
}
|
|
@ -77,6 +77,10 @@ const defaultColors = [
|
|||
"#8C33FF",
|
||||
];
|
||||
|
||||
// 添加逻辑分析仪频率常量
|
||||
const LOGIC_ANALYZER_FREQUENCY = 5_000_000; // 5MHz
|
||||
const SAMPLE_PERIOD_NS = 1_000_000_000 / LOGIC_ANALYZER_FREQUENCY; // 采样周期,单位:纳秒
|
||||
|
||||
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
||||
() => {
|
||||
const logicData = shallowRef<LogicDataType>();
|
||||
|
@ -305,10 +309,10 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
|
||||
// 5. 解析数据为8个通道的数字信号
|
||||
const sampleCount = bytes.length;
|
||||
const timeStep = 0.1; // 假设每个采样点间隔0.1ms
|
||||
const timeStepNs = SAMPLE_PERIOD_NS; // 每个采样点间隔200ns (1/5MHz)
|
||||
|
||||
// 创建时间轴
|
||||
const x = Array.from({ length: sampleCount }, (_, i) => i * timeStep);
|
||||
// 创建时间轴(转换为合适的单位)
|
||||
const x = Array.from({ length: sampleCount }, (_, i) => i * timeStepNs / 1000); // 转换为微秒
|
||||
|
||||
// 创建8个通道的数据
|
||||
const y: number[][] = Array.from(
|
||||
|
@ -329,7 +333,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
const logicData: LogicDataType = {
|
||||
x,
|
||||
y,
|
||||
xUnit: "ms",
|
||||
xUnit: "us", // 改为微秒单位
|
||||
};
|
||||
|
||||
setLogicData(logicData);
|
||||
|
@ -383,51 +387,51 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
|
||||
// 添加生成测试数据的方法
|
||||
const generateTestData = () => {
|
||||
const sampleRate = 10000; // 10kHz sampling
|
||||
const duration = 1;
|
||||
const sampleRate = LOGIC_ANALYZER_FREQUENCY; // 使用实际的逻辑分析仪频率
|
||||
const duration = 0.001; // 1ms的数据
|
||||
const points = Math.floor(sampleRate * duration);
|
||||
|
||||
const x = Array.from(
|
||||
{ length: points },
|
||||
(_, i) => (i / sampleRate) * 1000,
|
||||
); // time in ms
|
||||
(_, i) => (i * SAMPLE_PERIOD_NS) / 1000, // 时间轴,单位:微秒
|
||||
);
|
||||
|
||||
// Generate 8 channels with different digital patterns
|
||||
const y = [
|
||||
// Channel 0: Clock signal 100Hz
|
||||
// Channel 0: Clock signal 1MHz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((100 * i) / sampleRate) % 2,
|
||||
(_, i) => Math.floor((1_000_000 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 1: Clock/2 signal 50Hz
|
||||
// Channel 1: Clock/2 signal 500kHz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((50 * i) / sampleRate) % 2,
|
||||
(_, i) => Math.floor((500_000 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 2: Clock/4 signal 25Hz
|
||||
// Channel 2: Clock/4 signal 250kHz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((25 * i) / sampleRate) % 2,
|
||||
(_, i) => Math.floor((250_000 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 3: Clock/8 signal 12.5Hz
|
||||
// Channel 3: Clock/8 signal 125kHz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
|
||||
(_, i) => Math.floor((125_000 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 4: Data signal (pseudo-random pattern)
|
||||
Array.from({ length: points }, (_, i) =>
|
||||
Math.abs(Math.floor(Math.sin(i * 0.01) * 10) % 2),
|
||||
Math.abs(Math.floor(Math.sin(i * 0.001) * 10) % 2),
|
||||
),
|
||||
// Channel 5: Enable signal (periodic pulse)
|
||||
Array.from({ length: points }, (_, i) =>
|
||||
Math.floor(i / 50) % 10 < 3 ? 1 : 0,
|
||||
Math.floor(i / 250) % 10 < 3 ? 1 : 0,
|
||||
),
|
||||
// Channel 6: Reset signal (occasional pulse)
|
||||
Array.from({ length: points }, (_, i) =>
|
||||
Math.floor(i / 200) % 20 === 0 ? 1 : 0,
|
||||
Math.floor(i / 1000) % 20 === 0 ? 1 : 0,
|
||||
),
|
||||
// Channel 7: Status signal (slow changing)
|
||||
Array.from({ length: points }, (_, i) => Math.floor(i / 1000) % 2),
|
||||
Array.from({ length: points }, (_, i) => Math.floor(i / 5000) % 2),
|
||||
];
|
||||
|
||||
// 同时更新通道标签为更有意义的名称
|
||||
|
@ -448,7 +452,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
|
||||
// 设置逻辑数据
|
||||
enableAllChannels();
|
||||
setLogicData({ x, y, xUnit: "ms" });
|
||||
setLogicData({ x, y, xUnit: "us" }); // 改为微秒单位
|
||||
|
||||
alert?.success("测试数据生成成功", 2000);
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<div class="stat">
|
||||
<div class="stat-title">采样率</div>
|
||||
<div class="stat-value text-info">100MHz</div>
|
||||
<div class="stat-value text-info">5MHz</div>
|
||||
<div class="stat-desc">最大采样频率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue