完成多个器件桥接类型的渲染

This commit is contained in:
锦恢 2024-12-25 19:39:36 +08:00
parent 425c513635
commit 2f1d16c303
11 changed files with 308 additions and 277 deletions

View File

@ -23,4 +23,3 @@ resources
```
---

View File

@ -1,125 +1,137 @@
{
"creator": "Yosys 0.48+5 (git sha1 7a362f1f7, clang++ 18.1.2-wasi-sdk -Oz)",
"modules": {
"half_adder": {
"half_adder": {
"attributes": {
"top": "00000000000000000000000000000001",
"src": "/root/half_adder.v:1.1-9.10"
},
"ports": {
"a": {
"direction": "input",
"bits": [ 2 ]
},
"b": {
"direction": "input",
"bits": [ 3 ]
},
"c": {
"direction": "input",
"bits": [ 4 ]
},
"sum": {
"direction": "output",
"bits": [ 5 ]
},
"carry": {
"direction": "output",
"bits": [ 6 ]
}
},
"cells": {
"$auto$simplemap.cc:75:simplemap_bitop$81": {
"hide_name": 1,
"type": "$_AND_",
"parameters": {
},
"attributes": {
"top": "00000000000000000000000000000001",
"src": "/root/half_adder.v:1.1-8.10"
"src": "/root/half_adder.v:7.18-7.23"
},
"ports": {
"a": {
"direction": "input",
"bits": [
2
]
},
"b": {
"direction": "input",
"bits": [
3
]
},
"sum": {
"direction": "output",
"bits": [
4
]
},
"carry": {
"direction": "output",
"bits": [
5
]
}
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"cells": {
"$auto$simplemap.cc:75:simplemap_bitop$80": {
"hide_name": 1,
"type": "$_AND_",
"parameters": {},
"attributes": {
"src": "/root/half_adder.v:6.18-6.23"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [
2
],
"B": [
3
],
"Y": [
5
]
}
},
"$auto$simplemap.cc:75:simplemap_bitop$81": {
"hide_name": 1,
"type": "$_XOR_",
"parameters": {},
"attributes": {
"src": "/root/half_adder.v:7.16-7.21"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [
2
],
"B": [
3
],
"Y": [
4
]
}
}
},
"netnames": {
"a": {
"hide_name": 0,
"bits": [
2
],
"attributes": {
"src": "/root/half_adder.v:2.9-2.10"
}
},
"b": {
"hide_name": 0,
"bits": [
3
],
"attributes": {
"src": "/root/half_adder.v:3.9-3.10"
}
},
"carry": {
"hide_name": 0,
"bits": [
5
],
"attributes": {
"src": "/root/half_adder.v:4.10-4.15"
}
},
"sum": {
"hide_name": 0,
"bits": [
4
],
"attributes": {
"src": "/root/half_adder.v:5.10-5.13"
}
}
"connections": {
"A": [ 2 ],
"B": [ 3 ],
"Y": [ 7 ]
}
},
"$auto$simplemap.cc:75:simplemap_bitop$82": {
"hide_name": 1,
"type": "$_OR_",
"parameters": {
},
"attributes": {
"src": "/root/half_adder.v:7.18-7.27"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [ 7 ],
"B": [ 4 ],
"Y": [ 6 ]
}
},
"$auto$simplemap.cc:75:simplemap_bitop$83": {
"hide_name": 1,
"type": "$_XOR_",
"parameters": {
},
"attributes": {
"src": "/root/half_adder.v:8.16-8.21"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [ 2 ],
"B": [ 3 ],
"Y": [ 5 ]
}
}
},
"netnames": {
"$and$/root/half_adder.v:7$1_Y": {
"hide_name": 1,
"bits": [ 7 ],
"attributes": {
"src": "/root/half_adder.v:7.18-7.23"
}
},
"a": {
"hide_name": 0,
"bits": [ 2 ],
"attributes": {
"src": "/root/half_adder.v:2.9-2.10"
}
},
"b": {
"hide_name": 0,
"bits": [ 3 ],
"attributes": {
"src": "/root/half_adder.v:3.9-3.10"
}
},
"c": {
"hide_name": 0,
"bits": [ 4 ],
"attributes": {
"src": "/root/half_adder.v:4.9-4.10"
}
},
"carry": {
"hide_name": 0,
"bits": [ 6 ],
"attributes": {
"src": "/root/half_adder.v:5.10-5.15"
}
},
"sum": {
"hide_name": 0,
"bits": [ 5 ],
"attributes": {
"src": "/root/half_adder.v:6.10-6.13"
}
}
}
}
}
}
}

View File

@ -6,17 +6,22 @@
</template>
<script setup>
import { useI18n } from 'vue-i18n';
import Render from '@/components/render';
import RightNav from '@/components/right-nav.vue';
import { onMounted } from 'vue';
import { onMounted, watch } from 'vue';
import { setDefaultCss } from './hook/css';
import { NetlistRender } from './hook/render';
import * as d3 from 'd3';
import { globalLookup } from './hook/global';
import { globalLookup, globalSetting } from './hook/global';
// globalSetting localStorage
watch(
() => globalSetting,
() => {
localStorage.setItem('setting', JSON.stringify(globalSetting));
},
{ deep: true }
);
const { t } = useI18n();
onMounted(async () => {
// css
@ -29,7 +34,7 @@ onMounted(async () => {
const skinManager = globalLookup.skinManager;
skinManager.load(skinBinary);
render.load(netJson);
const layout = await render.createLayout();
const svg = await render.render(layout, '#netlist');

View File

@ -48,7 +48,6 @@
</span>
<el-switch
v-model="globalSetting.renderAnimation"
size="large"
active-text="ON"
inactive-text="OFF"
/>
@ -66,7 +65,7 @@ import { useI18n } from 'vue-i18n';
defineComponent({ name: "dide-setting" });
const { t, locale } = useI18n();
locale.value = globalSetting.language;
watch(
() => locale.value,
@ -85,7 +84,7 @@ function confirmLanguageDialog() {
function onlanguagechange(code) {
const option = languageSetting.options.find(item => item.value === code);
currentLanguage.value = option.text;
currentLanguage.value = option.text;
languageDialogShow.value = true;
}

View File

@ -22,20 +22,6 @@ export const globalLookup = {
*/
netlistRender: new NetlistRender(),
/**
* @type {number}
*/
svgScale: 1,
/**
* @type {number}
*/
svgTranslateX: 0,
/**
* @type {number}
*/
svgTranslateY: 0
};
function loadSetting() {

View File

@ -1,13 +1,16 @@
import * as d3 from 'd3';
import { globalSetting } from '../global';
import { NetlistRender } from '.';
export class CellRender {
/**
*
* @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/
constructor(selection, cells) {
constructor(selection, rootRender) {
this.parentSelection = selection;
this.rootRender = rootRender;
/**
* @type {BasicD3DataItem[]}
@ -33,6 +36,7 @@ export class CellRender {
render() {
const data = this.data;
const rootRender = this.rootRender;
let cellSelections = this.parentSelection.selectAll('g')
.data(data)
@ -53,19 +57,22 @@ export class CellRender {
.duration(1000)
.attr('stroke-opacity', 1)
.attr('class', 'grab')
.on('end', function (data) {
console.log('enter end');
.on('end', function (data) {
const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data);
registerDragEvent(cellSelection, data, rootRender);
});
} else {
cellSelections = cellSelections
.attr('class', 'grab')
.each(function (data) {
console.log('enter end'); // 在这里执行你需要的逻辑
const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data);
// 计算最小拓扑图
const mintoGraph = undefined;
registerDragEvent(cellSelection, data, rootRender);
});
}
@ -80,14 +87,15 @@ export class CellRender {
* 需要提取最小拓扑子图然后重新调整各个区域的尺寸
* @param {d3.Selection} selection
* @param {any} data
* @param {NetlistRender} rootRender
*/
export function registerDragEvent(selection, data) {
export function registerDragEvent(selection, data, rootRender) {
// 创建拖拽行为
const drag = d3.drag();
drag.on('start', event => dragStart(event, selection, data));
drag.on('drag', event => dragged(event, selection, data));
drag.on('end', event => dragEnd(event, selection, data))
drag.on('start', async event => dragStart(event, selection, data));
drag.on('drag', async event => dragged(event, selection, data, rootRender));
drag.on('end', async event => dragEnd(event, selection, data))
selection.call(drag);
}
@ -105,14 +113,20 @@ function dragStart(event, selection, data) {
*
* @param {d3.D3DragEvent} event
* @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/
function dragged(event, selection, data) {
function dragged(event, selection, data, rootRender) {
// 当拖动结束时D3 会根据绑定的数据data.x 和 data.y重新渲染元素导致元素回到初始位置。
// 所以需要 手动更新 data.x 和 data.y
data.x = event.x;
data.y = event.y;
selection
.attr('x', event.x)
.attr('y', event.y);
// 根据最小拓扑图,提取出关键点,重新计算布局
}
/**

View File

@ -31,11 +31,31 @@ export class NetlistRender {
edges: []
};
/**
* @type {import('elkjs').ELK}
*/
this.elk = new ELK();
/**
* @type {Map<string, Module>}
*/
this.nameToModule = new Map();
this.maxX = -1;
this.minX = 1e12;
this.maxY = -1;
this.minY = 1e12;
/**
* @type {number} 一开始绘制完成后整体的横向偏移量默认一开始从左上角开始画不居中
*/
this.startOffsetX = 0;
/**
* @type {number} 一开始绘制完成后整体的纵向偏移量默认一开始从左上角开始画不居中
*/
this.startOffsetY = 0;
}
/**
@ -48,16 +68,19 @@ export class NetlistRender {
*/
this.rawNet = rawNet;
// 转换为 elkjs 格式的 graph
// 构造模块树
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
const top = parseInt(rawModule.attributes.top);
// 一开始只渲染 top 模块
// 一开始只渲染 top 模块,或者告诉我当前的 top 是什么
if (top) {
const module = new Module(moduleName, rawModule);
// 构造符合 elk 格式的节点数据
const portNodes = module.makeNetsElkNodes();
const cellNodes = module.makeCellsElkNodes();
const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();
// 挂载到渲染图中
this.elkGraph.children.push(...portNodes);
this.elkGraph.children.push(...cellNodes);
this.elkGraph.children.push(...constantNodes);
@ -73,15 +96,21 @@ export class NetlistRender {
* @returns {Promise<ElkNode>}
*/
async createLayout() {
const elk = new ELK();
const graph = this.elkGraph;
const layoutGraph = await elk.layout(graph, {
const start = performance.now();
const layoutGraph = await this.elk.layout(graph, {
layoutOptions: {
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
'org.eclipse.elk.spacing.nodeNode': 35,
'org.eclipse.elk.layered.layering.strategy': 'LONGEST_PATH'
}
});
const timecost = (performance.now() - start).toFixed(3);
console.log(
`%c 布局计算耗时 ${timecost} ms`,
'background-color: #CB81DA; color: white; padding: 3px; border-radius: 3px;'
);
return layoutGraph;
}
@ -132,6 +161,8 @@ export class NetlistRender {
// 将分组作为一个后续操作的 parent selection
const g = svg.append('g');
console.log(computedLayout);
await this.renderLine(g, computedLayout, ratio);
await this.renderEntity(g, computedLayout, ratio);
@ -139,7 +170,10 @@ export class NetlistRender {
this.selection = svg;
// 注册平移和缩放
// this.registerRenderTransform(g);
this.registerRenderTransform(g);
// 根据最大最小尺寸微调全局方位
this.adjustLocation(g);
return svg;
}
@ -164,6 +198,9 @@ export class NetlistRender {
this.connectionRender = new ConnectionRender(parentSelection);
for (const node of computedLayout.children) {
// 只计算形体的,因为 连接点 非常小,几乎不影响布局
this.updateMaxMinSize(node.x, node.y, node.width, node.height);
const skin = skinManager.querySkin(node.renderName);
if (skin) {
// 具有 skin 的器件
@ -223,15 +260,16 @@ export class NetlistRender {
// 创建缩放行为
const zoom = d3.zoom()
// 设置缩放范围(最小缩放比例 0.5,最大缩放比例 5
.scaleExtent([0.5, 5])
.scaleExtent([0.1, 10])
.on("zoom", zoomed);
// 将缩放行为应用到 SVG
this.selection.call(zoom);
this.zoom = zoom;
// 缩放事件处理函数
function zoomed(event) {
const { transform, sourceEvent } = event;
const { transform, sourceEvent } = event;
if (sourceEvent && sourceEvent.type === "wheel") {
if (globalSetting.renderAnimation) {
@ -248,54 +286,64 @@ export class NetlistRender {
} else {
parentSelection.attr('transform', transform);
}
} else {
// 系统调用的变换,慢一点变换
if (globalSetting.renderAnimation) {
parentSelection
.transition()
.duration(1000)
.attr('transform', transform);
} else {
parentSelection.attr('transform', transform);
}
}
}
}
updateMaxMinSize(x, y, width, height) {
let x1 = x;
let y1 = y;
let x2 = x1 + width;
let y2 = y1 + height;
this.minX = Math.min(this.minX, x1);
this.maxX = Math.max(this.maxX, x2);
this.minY = Math.min(this.minY, y1);
this.maxY = Math.max(this.maxY, y2);
}
/**
* @description globalLookup 中更新 svg 的方位
* @description 调整位姿
* @param {d3.Selection} parentSelection
*/
updateLocationFromGlobal() {
// const svg = globalLookup.netlistRender.selection;
// if (!svg) {
// return;
adjustLocation(parentSelection) {
const renderHeight = this.renderHeight;
const renderWidth = this.renderWidth;
// TODO: 更加智能的方位调整
const cx = (this.minX + this.maxX) / 2;
const cy = (this.minY + this.maxY) / 2;
this.startOffsetX = renderWidth / 2 - cx;
this.startOffsetY = renderHeight / 2 - cy;
const transform = d3.zoomIdentity.translate(
this.startOffsetX, this.startOffsetY
);
this.selection.call(
this.zoom.transform,
transform
);
// if (globalSetting.renderAnimation) {
// parentSelection
// .transition()
// .duration(1000)
// .attr('transform', transform);
// } else {
// parentSelection.attr('transform', transform);
// }
// svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`);
}
/**
* @description 渲染 module 中的 port
*/
renderPorts() {
}
/**
* @description 渲染 module 中的例化模块
*/
renderInstantiations() {
}
/**
* @description 渲染 module 中的基础器件
*/
renderCells() {
}
/**
* @description 渲染每一个 例化模块/基础器件 的接入端口
*/
renderConnections() {
}
/**
* @description 渲染连线
*/
renderWires() {
}
}

View File

@ -3,7 +3,7 @@
*/
import { globalLookup } from "../global";
import { Cell, CELL_LIBS, ModuleTree } from "./yosys";
import { Cell, ModuleTree } from "./yosys";
export const SKIN_SCALE = 1;
export const LINE_WIDTH = 2;
@ -33,16 +33,9 @@ export const ELK_DIRECTION = {
BOTTOM: 'SOUTH'
}
/**
*
* @param {Cell} cell
*/
function getInstanceSize(cell) {
// 根据 port 尺寸来进行计算
}
function makeEdgeId(fromId, toId) {
return 'edge-' + fromId + '-' + toId;
export function makeEdgeId(fromId, toId) {
return fromId + '-' + toId;
}
export class Module {
@ -229,8 +222,6 @@ export class Module {
for (const cellName of tree.nameToCell.keys()) {
const cell = tree.nameToCell.get(cellName);
// 当前器件的 ID
const cellId = cell.id;
for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName);
@ -257,7 +248,7 @@ export class Module {
// 创建常数到器件的连线
const edge = {
id: makeEdgeId(cellId, id),
id: makeEdgeId(cell.id, id),
sources: [connection.id],
targets: [id]
};
@ -268,11 +259,14 @@ export class Module {
// 1. 当前的器件的这个端口和某一个 port 连接
// 2. 当前的器件的这个端口和另一个器件的一个端口连接
if (tree.wireIdToPort.has(wireId)) {
// 当前的器件的这个端口和某一个 port 连接
const port = tree.wireIdToPort.get(wireId);
if (port.direction === 'input') {
const edge = {
id: makeEdgeId(cellId, port.id),
// id 遵循 sourcePort-targetPort
id: makeEdgeId(port.id, cell.id),
source: port.id,
sourcePort: port.id,
target: cell.id,
@ -282,7 +276,7 @@ export class Module {
edges.push(edge);
} else {
const edge = {
id: makeEdgeId(cellId, port.id),
id: makeEdgeId(cell.id, port.id),
source: cell.id,
sourcePort: connection.id,
target: port.id,
@ -292,14 +286,32 @@ export class Module {
edges.push(edge);
}
} else if (tree.wireIdToConnection.has(wireId)) {
const connection = tree.wireIdToConnection.get(wireId);
const edge = {
id: makeEdgeId(cellId, connection.id),
sources: [cellId],
targets: [connection.id]
};
// 当前的器件的这个端口和另一个器件的一个端口连接
const conn = tree.wireIdToConnection.get(wireId);
edges.push(edge);
if (conn.direction === 'input') {
const edge = {
// id 遵循 sourcePort-targetPort
id: makeEdgeId(conn.id, cell.id),
source: conn.cell.id,
sourcePort: conn.id,
target: cell.id,
targetPort: connection.id
};
edges.push(edge);
} else {
const edge = {
id: makeEdgeId(cell.id, conn.id),
source: cell.id,
sourcePort: connection.id,
target: conn.id,
targetPort: conn.cell.id
};
edges.push(edge);
}
}
}
}

View File

@ -47,12 +47,10 @@ export class PortRender {
portSelections.append("text")
.attr("x", d => d.width / 2) // 文本的 x 坐标(居中)
.attr("y", d => d.height / 2) // 文本的 y 坐标(居中)
.attr("text-anchor", "middle") // 文本水平居中
.attr("dominant-baseline", "middle") // 文本垂直居中
.text(d => d.text) // 设置文本内容
.attr("fill", "black"); // 文本颜色
.attr("x", d => d.width / 2)
.attr("y", d => d.height / 2)
.text(d => d.text)
.attr('font-size', '12px');
if (globalSetting.renderAnimation) {
portSelections = portSelections

View File

@ -1,29 +1,3 @@
/**
* @type {Record<string, string>} yosys 器件映射
* - key: 器件的 type比如 $_AND_
* - value: 器件对应的我们内部的名称用于检索皮肤系统的
*/
export const CELL_LIBS = {
"$_AND_": "and",
"$_XOR_": "xor",
"$_NOT_": "not",
"$_OR_": "or",
"$_NAND_": "nand",
"$_NOR_": "nor",
"$_XNOR_": "xnor",
"$_MUX_": "mux",
"$_AOI3_": "aoi3",
"$_OAI3_": "oai3",
"$_AOI4_": "aoi4",
"$_OAI4_": "oai4",
"$_DFF_P_": "dff_pos",
"$_DFF_N_": "dff_neg",
"$_SR_NN_": "sr_nn",
"$_SR_NP_": "sr_np",
"$_SR_PN_": "sr_pn",
"$_SR_PP_": "sr_pp"
};
/**
* @description 模块树对象直接作为 treeview 的渲染视图加入运算
* 相比于 YosysNetModule, ModuleTree 只关心和渲染有关的那部分变量且不可被序列化

View File

@ -18,19 +18,11 @@ function windowsKeydown(element, event) {
if (event.ctrlKey) {
if (deltaY < 0) {
// scale up
const nextScale = globalLookup.svgScale + 0.1;
if (nextScale <= MAX_SCALE) {
globalLookup.svgScale = nextScale;
globalLookup.netlistRender.updateLocationFromGlobal();
}
} else if (deltaY > 0) {
// scale down
const nextScale = globalLookup.svgScale - 0.1;
if (nextScale >= MIN_SCALE) {
globalLookup.svgScale = nextScale;
globalLookup.netlistRender.updateLocationFromGlobal();
}
}
event.preventDefault();
@ -39,28 +31,20 @@ function windowsKeydown(element, event) {
} else if (deltaX !== 0 && deltaY === 0) {
if (deltaX > 0) {
// scroll left
const translateX = globalLookup.svgTranslateX - 50;
globalLookup.svgTranslateX = translateX;
globalLookup.netlistRender.updateLocationFromGlobal();
} else {
// scroll right
const translateX = globalLookup.svgTranslateX + 50;
globalLookup.svgTranslateX = translateX;
globalLookup.netlistRender.updateLocationFromGlobal();
}
event.preventDefault();
} else if (deltaX === 0 && deltaY !== 0) {
if (deltaY > 0) {
// scroll up
const translateY = globalLookup.svgTranslateY - 50;
globalLookup.svgTranslateY = translateY;
globalLookup.netlistRender.updateLocationFromGlobal();
} else {
// scroll down
const translateY = globalLookup.svgTranslateY + 50;
globalLookup.svgTranslateY = translateY;
globalLookup.netlistRender.updateLocationFromGlobal();
}
event.preventDefault();