+
-
@@ -148,7 +141,7 @@ defineExpose({
export function getDefaultProps() {
return {
size: 1,
- pins: [] // 默认不提供引脚,由computedPins计算生成
+ pins: [], // 默认不提供引脚,由computedPins计算生成
};
}
diff --git a/src/components/equipments/Pin.vue b/src/components/equipments/Pin.vue
index 63e6478..50f2fb7 100644
--- a/src/components/equipments/Pin.vue
+++ b/src/components/equipments/Pin.vue
@@ -1,30 +1,14 @@
-
@@ -167,20 +155,20 @@ defineExpose({
export function getDefaultProps() {
return {
size: 1,
- label: 'PIN',
- constraint: '',
- direction: 'input',
- type: 'digital',
- pinId: 'pin-default',
- componentId: '',
+ label: "PIN",
+ constraint: "",
+ direction: "input",
+ type: "digital",
+ pinId: "pin-default",
+ componentId: "",
pins: [
{
- pinId: 'PIN',
- constraint: '',
+ pinId: "PIN",
+ constraint: "",
x: 0,
- y: 0
- }
- ]
+ y: 0,
+ },
+ ],
};
}
@@ -190,18 +178,25 @@ export function getDefaultProps() {
display: block;
user-select: none;
position: relative;
- z-index: 5; /* 提高引脚组件的z-index */
- pointer-events: auto; /* 确保可以接收点击事件 */
- overflow: visible; /* 确保可以看到引脚 */
+ z-index: 5;
+ /* 提高引脚组件的z-index */
+ pointer-events: auto;
+ /* 确保可以接收点击事件 */
+ overflow: visible;
+ /* 确保可以看到引脚 */
}
+
.interactive {
cursor: pointer;
transition: filter 0.2s;
- pointer-events: auto; /* 确保可以接收点击事件 */
+ pointer-events: auto;
+ /* 确保可以接收点击事件 */
}
+
.interactive:hover {
filter: brightness(1.2);
- stroke: rgba(255, 255, 255, 0.3); /* 添加边框以便更容易看到点击区域 */
+ stroke: rgba(255, 255, 255, 0.3);
+ /* 添加边框以便更容易看到点击区域 */
stroke-width: 1;
}
diff --git a/src/components/equipments/SMT_LED.vue b/src/components/equipments/SMT_LED.vue
index c5c033a..d55bf8a 100644
--- a/src/components/equipments/SMT_LED.vue
+++ b/src/components/equipments/SMT_LED.vue
@@ -64,9 +64,11 @@
@@ -224,7 +201,9 @@ defineExpose({
}
.segment {
- transition: opacity 0.2s, fill 0.2s;
+ transition:
+ opacity 0.2s,
+ fill 0.2s;
}
/* 数码管发光效果 */
@@ -238,19 +217,19 @@ defineExpose({
export function getDefaultProps() {
return {
size: 1,
- color: 'red',
- cathodeType: 'common',
+ color: "red",
+ cathodeType: "common",
pins: [
- { pinId: 'a', constraint: '', x: 10 , y: 170 },
- { pinId: 'b', constraint: '', x: 25-1 , y: 170 },
- { pinId: 'c', constraint: '', x: 40-2 , y: 170 },
- { pinId: 'd', constraint: '', x: 55-3 , y: 170 },
- { pinId: 'e', constraint: '', x: 70-4 , y: 170 },
- { pinId: 'f', constraint: '', x: 85-5 , y: 170 },
- { pinId: 'g', constraint: '', x: 100-6, y: 170 },
- { pinId: 'dp', constraint: '', x: 115-7, y: 170 },
- { pinId: 'COM', constraint: '', x: 60 , y: 10 }
- ]
+ { pinId: "a", constraint: "", x: 10, y: 170 },
+ { pinId: "b", constraint: "", x: 25 - 1, y: 170 },
+ { pinId: "c", constraint: "", x: 40 - 2, y: 170 },
+ { pinId: "d", constraint: "", x: 55 - 3, y: 170 },
+ { pinId: "e", constraint: "", x: 70 - 4, y: 170 },
+ { pinId: "f", constraint: "", x: 85 - 5, y: 170 },
+ { pinId: "g", constraint: "", x: 100 - 6, y: 170 },
+ { pinId: "dp", constraint: "", x: 115 - 7, y: 170 },
+ { pinId: "COM", constraint: "", x: 60, y: 10 },
+ ],
};
}
diff --git a/src/components/equipments/Wire.vue b/src/components/equipments/Wire.vue
index 3361727..60770c1 100644
--- a/src/components/equipments/Wire.vue
+++ b/src/components/equipments/Wire.vue
@@ -1,28 +1,18 @@
-
+
- {{ props.constraint || '' }}
+ {{ props.constraint || ""
+ }}
diff --git a/src/components/equipments/componentConfig.ts b/src/components/equipments/componentConfig.ts
index fcb4c6c..3e7e184 100644
--- a/src/components/equipments/componentConfig.ts
+++ b/src/components/equipments/componentConfig.ts
@@ -183,4 +183,4 @@ export function generatePropsFromAttrs(attrs: Record
): PropertyConf
export function getPropValue(component: DiagramPart, propName: string): any {
if (!component) return undefined;
return (component as any)[propName];
-}
\ No newline at end of file
+}
diff --git a/src/stores/constraints.ts b/src/stores/constraints.ts
index f09783b..ddaa4d8 100644
--- a/src/stores/constraints.ts
+++ b/src/stores/constraints.ts
@@ -1,81 +1,103 @@
-import { ref, reactive } from 'vue';
+import { ref, computed, reactive } from 'vue'
+import { defineStore } from 'pinia'
+import { isBoolean } from 'lodash';
+
// 约束电平状态类型
export type ConstraintLevel = 'high' | 'low' | 'undefined';
-// 约束状态存储
-const constraintStates = reactive>({});
+export const useConstraintsStore = defineStore('constraints', () => {
-// 约束颜色映射
-export const constraintColors = {
- high: '#ff3333', // 高电平为红色
- low: '#3333ff', // 低电平为蓝色
- undefined: '#999999' // 未定义为灰色
-};
+ // 约束状态存储
+ const constraintStates = reactive>({});
-// 获取约束状态
-export function getConstraintState(constraint: string): ConstraintLevel {
- if (!constraint) return 'undefined';
- return constraintStates[constraint] || 'undefined';
-}
-
-// 设置约束状态
-export function setConstraintState(constraint: string, level: ConstraintLevel) {
- if (!constraint) return;
- constraintStates[constraint] = level;
-}
-
-// 批量设置约束状态
-export function batchSetConstraintStates(states: Record) {
- // 收集发生变化的约束
- const changedConstraints: [string, ConstraintLevel][] = [];
-
- // 更新状态并收集变化
- Object.entries(states).forEach(([constraint, level]) => {
- if (constraintStates[constraint] !== level) {
- constraintStates[constraint] = level;
- changedConstraints.push([constraint, level]);
- }
- });
-
- // 通知所有变化
- changedConstraints.forEach(([constraint, level]) => {
- stateChangeCallbacks.forEach(callback => callback(constraint, level));
- });
-}
-
-// 获取约束对应的颜色
-export function getConstraintColor(constraint: string): string {
- const state = getConstraintState(constraint);
- return constraintColors[state];
-}
-
-// 清除所有约束状态
-export function clearAllConstraintStates() {
- Object.keys(constraintStates).forEach(key => {
- delete constraintStates[key];
- });
-}
-
-// 获取所有约束状态
-export function getAllConstraintStates(): Record {
- return { ...constraintStates };
-}
-
-// 注册约束状态变化回调
-const stateChangeCallbacks: ((constraint: string, level: ConstraintLevel) => void)[] = [];
-
-export function onConstraintStateChange(callback: (constraint: string, level: ConstraintLevel) => void) {
- stateChangeCallbacks.push(callback);
- return () => {
- const index = stateChangeCallbacks.indexOf(callback);
- if (index > -1) {
- stateChangeCallbacks.splice(index, 1);
- }
+ // 约束颜色映射
+ const constraintColors = {
+ high: '#ff3333', // 高电平为红色
+ low: '#3333ff', // 低电平为蓝色
+ undefined: '#999999' // 未定义为灰色
};
-}
-// 触发约束变化
-export function notifyConstraintChange(constraint: string, level: ConstraintLevel) {
- setConstraintState(constraint, level);
- stateChangeCallbacks.forEach(callback => callback(constraint, level));
-}
+ // 获取约束状态
+ function getConstraintState(constraint: string): ConstraintLevel {
+ if (!constraint) return 'undefined';
+ return constraintStates[constraint] || 'undefined';
+ }
+
+ // 设置约束状态
+ function setConstraintState(constraint: string, level: ConstraintLevel) {
+ if (!constraint) return;
+ constraintStates[constraint] = level;
+ }
+
+ // 批量设置约束状态
+ function batchSetConstraintStates(states: Record | Record) {
+ // 收集发生变化的约束
+ const changedConstraints: [string, ConstraintLevel][] = [];
+
+ // 更新状态并收集变化
+ Object.entries(states).forEach(([constraint, level]) => {
+ if (isBoolean(level)) {
+ level = level ? "high" : "low";
+ }
+
+ if (constraintStates[constraint] !== level) {
+ constraintStates[constraint] = level;
+ changedConstraints.push([constraint, level]);
+ }
+ });
+
+ // 通知所有变化
+ changedConstraints.forEach(([constraint, level]) => {
+ stateChangeCallbacks.forEach(callback => callback(constraint, level));
+ });
+ }
+
+ // 获取约束对应的颜色
+ function getConstraintColor(constraint: string): string {
+ const state = getConstraintState(constraint);
+ return constraintColors[state];
+ }
+
+ // 清除所有约束状态
+ function clearAllConstraintStates() {
+ Object.keys(constraintStates).forEach(key => {
+ delete constraintStates[key];
+ });
+ }
+
+ // 获取所有约束状态
+ function getAllConstraintStates(): Record {
+ return { ...constraintStates };
+ }
+
+ // 注册约束状态变化回调
+ const stateChangeCallbacks: ((constraint: string, level: ConstraintLevel) => void)[] = [];
+
+ function onConstraintStateChange(callback: (constraint: string, level: ConstraintLevel) => void) {
+ stateChangeCallbacks.push(callback);
+ return () => {
+ const index = stateChangeCallbacks.indexOf(callback);
+ if (index > -1) {
+ stateChangeCallbacks.splice(index, 1);
+ }
+ };
+ }
+
+ // 触发约束变化
+ function notifyConstraintChange(constraint: string, level: ConstraintLevel) {
+ setConstraintState(constraint, level);
+ stateChangeCallbacks.forEach(callback => callback(constraint, level));
+ }
+
+ return {
+ getConstraintState,
+ setConstraintState,
+ batchSetConstraintStates,
+ getConstraintColor,
+ clearAllConstraintStates,
+ getAllConstraintStates,
+ onConstraintStateChange,
+ notifyConstraintChange,
+ }
+})
+
diff --git a/src/stores/equipments.ts b/src/stores/equipments.ts
index f7c4425..0233356 100644
--- a/src/stores/equipments.ts
+++ b/src/stores/equipments.ts
@@ -1,22 +1,157 @@
-import { ref, computed } from 'vue'
+import { ref, watchEffect } from 'vue'
import { defineStore } from 'pinia'
-import { isUndefined } from 'lodash';
+import { isString, toNumber, isUndefined } from 'lodash';
+import { Common } from '@/Common';
+import z from "zod"
+import { isNumber } from 'mathjs';
+import { JtagClient } from "@/APIClient";
+import { Mutex, withTimeout } from 'async-mutex';
+import { useConstraintsStore } from "@/stores/constraints";
+import { useDialogStore } from './dialog';
export const useEquipments = defineStore('equipments', () => {
- const jtagIPAddr = ref("127.0.0.1")
- const jtagPort = ref("1234")
- const jtagBitstream = ref()
- const remoteUpdateIPAddr = ref("127.0.0.1")
- const remoteUpdatePort = ref("1234")
- const remoteUpdateBitstream = ref()
+ // Global Stores
+ const constrainsts = useConstraintsStore();
+ const dialog = useDialogStore();
+
+ const boardAddr = ref("127.0.0.1");
+ const boardPort = ref(1234);
+ const jtagBitstream = ref();
+ const jtagBoundaryScanFreq = ref(100);
+ const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!"))
+ const jtagClient = new JtagClient();
+
+ const enableJtagBoundaryScan = ref(false);
+
+ function setAddr(address: string | undefined): boolean {
+ if (isString(address) && z.string().ip("4").safeParse(address).success) {
+ boardAddr.value = address;
+ return true;
+ }
+
+ return false;
+ }
+
+ function setPort(port: string | number | undefined): boolean {
+ if (isString(port) && port.length != 0) {
+ const portNumber = toNumber(port);
+ if (z.number().nonnegative().max(65535).safeParse(portNumber).success) {
+ boardPort.value = portNumber;
+ return true;
+ }
+ }
+ else if (isNumber(port)) {
+ if (z.number().nonnegative().max(65535).safeParse(port).success) {
+ boardPort.value = port;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ watchEffect(() => {
+ if (enableJtagBoundaryScan.value) jtagBoundaryScan();
+ });
+
+ async function jtagBoundaryScan() {
+ const release = await jtagClientMutex.acquire();
+ try {
+ const portStates = await jtagClient.boundaryScanLogicalPorts(
+ boardAddr.value,
+ boardPort.value,
+ );
+
+ constrainsts.batchSetConstraintStates(portStates);
+ } catch (error) {
+ dialog.error("边界扫描发生错误");
+ console.error(error);
+ enableJtagBoundaryScan.value = false;
+ } finally {
+ release();
+
+ if (enableJtagBoundaryScan.value)
+ setTimeout(jtagBoundaryScan, 1000 / jtagBoundaryScanFreq.value);
+ }
+ }
+
+ async function jtagUploadBitstream(bitstream: File): Promise {
+ try {
+ const resp = await jtagClient.uploadBitstream(
+ boardAddr.value,
+ Common.toFileParameterOrNull(bitstream),
+ );
+ return resp;
+ } catch (e) {
+ dialog.error("上传错误");
+ console.error(e);
+ return false;
+ }
+ }
+
+ async function jtagDownloadBitstream(): Promise {
+ const release = await jtagClientMutex.acquire();
+ try {
+ const resp = await jtagClient.downloadBitstream(
+ boardAddr.value,
+ boardPort.value
+ );
+ return resp;
+ } catch (e) {
+ dialog.error("上传错误");
+ console.error(e);
+ return false;
+ } finally {
+ release();
+ }
+ }
+
+ async function jtagGetIDCode(isQuiet: boolean = false): Promise {
+ const release = await jtagClientMutex.acquire();
+ try {
+ const resp = await jtagClient.getDeviceIDCode(
+ boardAddr.value,
+ boardPort.value
+ );
+ return resp;
+ } catch (e) {
+ if (!isQuiet) dialog.error("获取IDCode错误");
+ return 0xffff_ffff;
+ } finally {
+ release();
+ }
+ }
+
+ async function jtagSetSpeed(speed: number): Promise {
+ const release = await jtagClientMutex.acquire();
+ try {
+ const resp = await jtagClient.setSpeed(
+ boardAddr.value,
+ boardPort.value,
+ speed
+ );
+ return resp;
+ } catch (e) {
+ dialog.error("设置Jtag速度失败");
+ return false;
+ } finally {
+ release();
+ }
+ }
return {
- jtagIPAddr,
- jtagPort,
+ boardAddr,
+ boardPort,
+ setAddr,
+ setPort,
jtagBitstream,
- remoteUpdateIPAddr,
- remoteUpdatePort,
- remoteUpdateBitstream,
+ jtagBoundaryScanFreq,
+ jtagClientMutex,
+ jtagClient,
+ jtagUploadBitstream,
+ jtagDownloadBitstream,
+ jtagGetIDCode,
+ jtagSetSpeed,
+ enableJtagBoundaryScan,
}
})
diff --git a/src/views/AdminView.vue b/src/views/AdminView.vue
index 5d62def..9b3b625 100644
--- a/src/views/AdminView.vue
+++ b/src/views/AdminView.vue
@@ -1,102 +1,107 @@
-
FPGA 设备管理
-
+
+
FPGA 设备管理
+
+
+
-
IP 地址列表
-
+
+
IP 地址列表
+
+
+
-
+
- 提示:
+ 提示:
请谨慎操作FPGA固化和热启动功能,确保上传的位流文件无误,以避免设备损坏。
@@ -106,21 +111,139 @@
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index db36d82..c817949 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,77 +1,88 @@
-