diff --git a/src/hook/jsdoc.js b/src/hook/jsdoc.js index 6a3bb4b..5c80c9b 100644 --- a/src/hook/jsdoc.js +++ b/src/hook/jsdoc.js @@ -5,14 +5,65 @@ * @property {Record} [models] - 模型定义的映射(仅在使用 `-aig` 选项时存在)。 */ +/** + * @typedef {number | string} WireId 信号ID + */ + +/** + * @typedef {'input' | 'output' | 'inout'} PortDirection + */ + +/** + * @typedef {string} ParameterValue 参数值,一般表示为二进制的字符串,比如 "00000000000000000000000000001000" 表示 8 + */ + /** * @typedef YosysNetModule * @property {ModuleAttribute} attributes - 模块的属性,如是否为顶层模块及其源文件。 - * @property {Record} [parameter_default_values] - 参数的默认值。 + * @property {Record} [parameter_default_values] - 参数的默认值。 * @property {Record} ports - 端口名称到端口定义的映射。 * @property {Record} cells - 单元名称到单元定义的映射。 * @property {Record} [memories] - 内存名称到内存定义的映射。 - * @property {Record} netnames - 网络名称到网络定义的映射。 + * @property {Record} netnames 信号名称到网络定义的映射。 + * + * 【信号名称】大体分为两种: + * 1. 普通信号 + * 2. 中间变量信号 + * + * ### 普通信号名 + * 一般是 module 的 port 和 module scope 内定义的各类变量,它们都有自己的名字。 + * + * ```json + * "A": { + "hide_name": 0, + "bits": [ 2 ], + "attributes": { ... } + } + * ``` + * 此处信号名称就是 A,它可能代表当前 module 的一个 port 的名字,也可也是一个内部定义的 wire 或者 reg + * + * ### 中间变量信号 + * 一些操作比如 `assign x = A & B | C` 这样的操作会产生中间变量,比如 A & B 会产生一个中间值,这个中间值在与 C 去发生计算 + * yosys 中使用如下的格式来代表这样的中间变量: + * + * `"${op_name}${define_path}:{define_line}${op_id}_{op_output_name}"` + * + * - `op_name`: 操作名称,比如 `&` 就是 `and` + * - `define_path`: 发生这个操作的文件路径 + * - `define_line`: 发生这个操作的行 + * - `op_id`: 当前操作在当前 module 内的编号 + * - `op_output_name`: 当前操作的输出值(有的操作会有多个输出,一般只有一个输出的简单操作,输出的 name 都是 Y) + * + * + * ```json + * "$xor$/home/dide/project/Digital-Test/DIDEtemp/user/src/language/vlog/dependence/utils/utils.v:32$4_Y": { + "hide_name": 1, + "bits": [ 11 ], + "attributes": { ... } + } + * ``` + * + * */ /** @@ -24,23 +75,23 @@ */ /** - * @typedef YosysPort - * @property {string} direction - 端口的方向(例如 "input"、"output" 或 "inout")。 - * @property {number[]} bits - 与端口关联的位索引。 + * @typedef YosysPort 描述一个 module 的 port + * @property {PortDirection} direction - 端口的方向(例如 "input"、"output" 或 "inout")。 + * @property {WireId[]} bits - 当前 port 每一个位置 bit 的信号 ID. bits.length * @property {number} [offset] - 使用的最低位索引(如果非 0)。 * @property {number} [upto] - 如果端口位索引是 MSB-first,则为 1。 * @property {number} [signed] - 如果端口是有符号的,则为 1。 */ /** - * @typedef YosysCell - * @property {number} hide_name - 是否隐藏单元名称(1 表示是,0 表示否)。 - * @property {string} type - 单元的类型(例如 "$_AND_" 或 "$_XOR_")。 + * @typedef YosysCell 描述一个基本器件 + * @property {number} hide_name - 当前器件是否有具体的名字,如果有则为 1,否则为 0, 加减乘除都是无名器件,一般都为 0,例化模块都是有名字的,一般都为 1 + * @property {string} type - 器件的类型,例如 "$_AND_" 代表 yosys 内置的与门, full_adder_1bit 代表当前器件是一个单位全加器的例化模块 * @property {string} [model] - AIG 模型名称(仅在使用 `-aig` 选项时存在)。 - * @property {Record} [parameters] - 与单元关联的参数(如果有)。 + * @property {Record} [parameters] - 与单元关联的参数(如果有)。 * @property {CellAttribute} attributes - 单元的属性,如源文件和行号。 - * @property {Record} port_directions - 端口名称到端口方向的映射(例如 "input"、"output" 或 "inout")。 - * @property {Record} connections - 端口名称到连接位索引的映射。 + * @property {Record} port_directions - 端口名称到端口方向的映射(例如 "input"、"output" 或 "inout")。 + * @property {Record} connections - 端口名称到 信号ID 的映射。 */ /** @@ -58,9 +109,9 @@ */ /** - * @typedef YosysNetname - * @property {number} hide_name - 是否隐藏网络名称(1 表示是,0 表示否)。 - * @property {number[]} bits - 与网络名称关联的位索引。 + * @typedef YosysNetname 一个模块内部的所有变量(port,临时变量等等) + * @property {number} hide_name - 同上 + * @property {WireId[]} bits - 与网络名称关联的位索引。 * @property {number} [offset] - 使用的最低位索引(如果非 0)。 * @property {number} [upto] - 如果端口位索引是 MSB-first,则为 1。 * @property {number} [signed] - 如果端口是有符号的,则为 1。 @@ -78,4 +129,51 @@ * @property {number} [bitindex] - 位索引(如果节点类型为 "port" 或 "nport")。 * @property {number} [node_index] - 节点索引(如果节点类型为 "and" 或 "nand")。 * @property {Array} [out_list] - 输出端口名称和位索引的列表。 + */ + + +/** + * @typedef ElkGraph + * @property {string} id - 图形的唯一标识符。 + * @property {ElkNode[]} children - 图形的子节点列表。 + * @property {ElkEdge[]} edges - 图形中的边列表。 + * @property {ElkLayoutOptions} [layoutOptions] - 图形的布局选项(可选)。 + */ + +/** + * @typedef ElkNode + * @property {string} id - 节点的唯一标识符。 + * @property {number} [x] - 节点的 X 坐标(可选,由布局算法生成)。 + * @property {number} [y] - 节点的 Y 坐标(可选,由布局算法生成)。 + * @property {number} [width] - 节点的宽度(可选)。 + * @property {number} [height] - 节点的高度(可选)。 + * @property {ElkPort[]} [ports] - 节点的端口列表(可选)。 + * @property {ElkLayoutOptions} [layoutOptions] - 节点的布局选项(可选)。 + */ + +/** + * @typedef ElkPort + * @property {string} id - 端口的唯一标识符。 + * @property {number} [x] - 端口的 X 坐标(可选,由布局算法生成)。 + * @property {number} [y] - 端口的 Y 坐标(可选,由布局算法生成)。 + * @property {number} [width] - 端口的宽度(可选)。 + * @property {number} [height] - 端口的高度(可选)。 + * @property {ElkLayoutOptions} [layoutOptions] - 端口的布局选项(可选)。 + */ + +/** + * @typedef ElkEdge + * @property {string} id - 边的唯一标识符。 + * @property {string[]} sources - 边的源节点列表。 + * @property {string[]} targets - 边的目标节点列表。 + * @property {ElkLayoutOptions} [layoutOptions] - 边的布局选项(可选)。 + */ + +/** + * @typedef ElkLayoutOptions + * @property {string} [elk.algorithm] - 布局算法(可选)。 + * @property {string} [elk.direction] - 布局方向(可选,如 "RIGHT", "DOWN" 等)。 + * @property {number} [elk.spacing.nodeNode] - 节点之间的间距(可选)。 + * @property {number} [elk.spacing.edgeNode] - 边与节点之间的间距(可选)。 + * @property {string} [elk.port.side] - 端口的位置(可选,如 "NORTH", "SOUTH", "EAST", "WEST")。 */ \ No newline at end of file diff --git a/src/hook/render/index.js b/src/hook/render/index.js index 3a5624e..40484b4 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -1,7 +1,39 @@ import * as d3 from 'd3'; +import { ModuleLayout } from './layout'; class NetlistRender { - constructor() { - + /** + * + * @param {YosysRawNet} rawNet + */ + constructor(rawNet) { + /** + * @type {YosysRawNet} + */ + this.rawNet = rawNet; + + /** + * @type {ElkGraph} + */ + this.elkGraph = { + id: 'root', + children: [], + edges: [] + }; + + this.loadYosysRawNet(rawNet); } + + /** + * @description 加载 yosys 格式的 json + * @param {YosysRawNet} rawNet + */ + loadYosysRawNet(rawNet) { + + // 转换为 elkjs 格式的 graph + for (const [moduleName, module] of Object.entries(rawNet.modules)) { + + } + } + } \ No newline at end of file diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js new file mode 100644 index 0000000..d12a7c6 --- /dev/null +++ b/src/hook/render/layout.js @@ -0,0 +1,212 @@ +/** + * 将各个节点转换为 elknode 的数据结构 + */ + +import { DEVICE_MAPPER } from "./yosys"; + +export const LAYOUT_CONSTANT = { + PORT_WIDTH: 50, + PORT_HEIGHT: 50, + INSTANTIATION_WIDTH: 50, + INSTANTIATION_HEIGHT: 50, + +}; + +export class ModuleLayout { + /** + * @param {string} name + * @param {YosysNetModule} module + */ + constructor(name, module) { + this.module = module; + this.name = name; + + this.initialise(); + } + + /** + * @description 初始化,构建信息更加丰富的 RawYosysNet + */ + initialise() { + // 构建模块树 + + + // 构建 wireId 到模块树节点的映射表 + + } + + /** + * @description 从 ports 和 netnames 中创建所有变量相关的节点 + * @returns {ElkNode[]} + */ + makeNetsElkNodes() { + const ports = this.module.ports; + const nets = this.module.netnames; + + const moduleName = this.name; + const nodes = []; + + // 绘制 ports + for (const [portName, port] of Object.entries(ports)) { + const portId = ElkNodeId.port(moduleName, portName); + // TODO: 把当前的 id 绑定到模块树上面 + const node = { + id: portId, + width: LAYOUT_CONSTANT.PORT_WIDTH, + height: LAYOUT_CONSTANT.PORT_HEIGHT, + layoutOptions: { + "elk.port.side": port.direction === "input" ? "WEST" : "EAST" + } + } + nodes.push(node); + } + + // 非 port 的其他内部变量的节点不需要绘制,因为它们总能被表示为中间变量 + // 它们的操作可以表示为一系列器件的组合或者直连 + + return nodes; + } + + /** + * @description 从 cells 中创建节点 + * @returns {ElkNode[]} + */ + makeCellsElkNodes() { + const cells = this.module.cells; + const moduleName = this.name; + const nodes = []; + for (const [cellName, cell] of Object.entries(cells)) { + const cellId = ElkNodeId.cell(moduleName, cellName); + // 查看当前的器件是否为 内置器件 + if (DEVICE_MAPPER[cell.type]) { + // 是内置器件 + // TODO: 查询内置器件的 size + const node = { + id: cellId, + width: 50, + height: 50, + layoutOptions: { + "elk.port.side": "NORTH" + } + }; + nodes.push(node); + } else { + // 是例化模块 + const node = { + id: cellId, + width: LAYOUT_CONSTANT.INSTANTIATION_WIDTH, + height: LAYOUT_CONSTANT.INSTANTIATION_HEIGHT, + layoutOptions: { + "elk.port.side": "NORTH" + } + }; + nodes.push(node); + } + } + + return nodes; + } + + /** + * @description 从 cells.connections 中创建边 + * @returns {[ElkNode[], ElkEdge[]]} + */ + makeConnectionElkNodes() { + const cells = this.module.cells; + const moduleName = this.name; + const nodes = []; + const edges = []; + + for (const [cellName, cell] of Object.entries(cells)) { + // 当前器件的 ID + const cellId = `${moduleName}.${cellName}`; + + for (const [connectionName, wireIds] of Object.entries(cell.connections)) { + // 器件当前的端口的 ID,为了区别与外层 module 的端口,起名为 connection + const connectionId = `${moduleName}.${cellName}.${connectionName}`; + + // 遍历器件端口的每一个连接点 + // 比如对于端口 input [31:0] data ,它的32个位不一定是完全导向一个变量(虽然我们愿意认为,大部分情况下是这样) + // 大部分情况下,👇的 wireIds.length 都是 1 + for (let i = 0; i < wireIds.length; ++ i) { + const bit = wireIds[i]; + // bit 可以是一个 id,也可以是一个常数 + if (typeof bit === 'string') { + // 常数 + // 如果是常数,需要先创建代表常数的节点,常数一定是器件的输入,而非输出 + const constant = parseInt(bit); + const constantBitId = ElkNodeId.constantBit(moduleName, cellName, connectionName, i, constant); + + const node = { + id: constantBitId, + width: 50, + height: 50, + layoutOptions: { + "elk.port.side": "WEST" + } + }; + + nodes.push(node); + + // 创建常数到器件的连线 + const edge = { + id: constantBitId, + sources: [cellId], + targets: [node.id] + }; + + // TODO: 把创建的 edge 关联到模块树上 + edges.push(edge); + + } else { + // 如果不是一个常数连接,那么存在两种可能 + // 1. 当前的器件的这个端口和某一个 port 连接 + // 2. 当前的器件的这个端口和另一个器件的一个端口连接 + + + // TODO: 把创建的 edge 关联到模块树上 + } + } + } + } + + return [nodes, edges]; + } + + +} + +/** + * @description 使用 . 把各个部分连接起来 + * @param {string[]} args + */ +export function dotConnect(...args) { + const stringArgs = args.map(arg => arg.toString()); + return stringArgs.join('.'); +} + +export namespace ElkNodeId { + export function module(moduleName) { + return dotConnect(moduleName); + } + + export function port(moduleName, portName) { + return dotConnect(moduleName, portName); + } + + export function cell(moduleName, cellName) { + return dotConnect(moduleName, cellName); + } + + export function connection(moduleName, cellName, connectionName) { + return dotConnect(moduleName, cellName, connectionName); + } + + export function constantBit(moduleName, cellName, connectionName, orderId, constant) { + return dotConnect(moduleName, cellName, connectionName, orderId, 'constant-' + constant); + } + + export function commonBit(moduleName, cellName, connectionName, wireId) { + return dotConnect(moduleName, cellName, connectionName, wireId); + } +} \ No newline at end of file diff --git a/src/hook/render/yosys.js b/src/hook/render/yosys.js new file mode 100644 index 0000000..0a05feb --- /dev/null +++ b/src/hook/render/yosys.js @@ -0,0 +1,134 @@ +/** + * @type {Record} yosys 器件映射 + * - key: 器件的 type,比如 $_AND_ + * - value: 器件对应的我们内部的名称,用于检索皮肤系统的 + */ +export const DEVICE_MAPPER = { + "$_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 只关心和渲染有关的那部分变量,且不可被序列化 + */ +export class ModuleTree { + /** + * @param {string} name + * @param {YosysNetModule} rawModule + */ + constructor(name, rawModule) { + this.name = name; + this.rawModule = rawModule; + } + + get id() { + return this.name; + } +} + +export class Port { + /** + * @description port 的抽象 + * @param {ModuleTree} moduleTree + * @param {string} name + * @param {YosysPort} rawPort + */ + constructor(moduleTree, name, rawPort) { + this.moduleTree = moduleTree; + this.name = name; + this.rawPort = rawPort; + } + + get id() { + return dotConnect(this.moduleTree.id, this.name); + } +} + +export class Cell { + /** + * @description 器件的抽象 + * @param {ModuleTree} moduleTree + * @param {string} name + * @param {YosysCell} rawCell + */ + constructor(moduleTree, name, rawCell) { + this.moduleTree = moduleTree; + this.name = name; + this.rawCell = rawCell; + } + + get id() { + return dotConnect(this.moduleTree.id, this.name); + } +} + +export class Connection { + /** + * @description connection 的抽象,一个 connection 代表一个器件的一个端口是如何与外界连接的 + * @param {Cell} cell + * @param {string} name + * @param {WireId[]} wireIds + */ + constructor(cell, name, wireIds) { + this.cell = cell; + this.name = name; + this.wireIds = wireIds; + + + } + + get id() { + return dotConnect(this.cell.id, this.name); + } +} + +export class Wire { + /** + * @description 一根线的抽象 + * @param {Connection} connection + * @param {number} index + * @param {WireId} wireId + */ + constructor(connection, index, wireId) { + this.connection = connection; + this.index = index; + this.wireId = wireId; + } + + get id() { + if (typeof this.wireId === 'string') { + // 常数连接 + const num = parseInt(this.wireId); + return dotConnect(this.connection.id, this.index, 'c-' + num); + } else { + // 普通连接 + return dotConnect(this.connection.id, this.wireId); + } + } +} + +/** + * @description 使用 . 把各个部分连接起来 + * @param {any[]} args + */ +export function dotConnect(...args) { + const stringArgs = args.map(arg => arg.toString()); + return stringArgs.join('.'); +} \ No newline at end of file diff --git a/src/static/analog.svg b/src/static/analog.svg new file mode 100644 index 0000000..b4650f1 --- /dev/null +++ b/src/static/analog.svg @@ -0,0 +1,333 @@ + + + + + + + + + + name + + + + + +\t +\t name +\t +\t +\t + + + + name + + + + + + + + input + + + + + + + output + + + + + + + + + + X1 + Xk + + + + + + + + + X1 + Xk + + + + + + + + + X1 + Xu + + + + + + + + + X1 + Xu + + + + + + + + + X1 + XpF + + + + + + + + + X1 + XpF + + + + + + + + + + + X1 + XV + + + + + + + + + + X1 + XA + + + + + + + + + + + + X1 + + + + + + + + + X1 + + + + + + + + + X1 + + + + + + + + + + + X1 + + + + + + + + + + + X1 + + + + + + + + + + + + + + X1 + + + + + + + + + + + + + + + + X1 + + + + + + + + + + + + + + X1 + + + + + + + + + + + + + + + generic + + + out0 + + + out1 + + + in0 + + + in1 + + + + + + + + X1 + + + + + + + + + + + + + + X1 + + + + + + + + + X1 + + + + + + + + + + + + diff --git a/src/static/digital.svg b/src/static/digital.svg new file mode 100644 index 0000000..28c3cb8 --- /dev/null +++ b/src/static/digital.svg @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + A + B + + + + + + + + + + + + + + + + + + + + and + + + + + + + + + + + + + + + + nand + + + + + + + + + + + + + + + + + + or + + + + + + + + + + + + + + + + + nor + + + + + + + + + + + + + + + + xor + + + + + + + + + + + + + + + nxor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D + Q + CLK + dff + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D + Q + CLK + dffs + + + + + + + + + + + + + + + D + Q + CLK + dffn + + + + + + + + + + + + + + + + + D + Q + CLK + dffns + + + + + + + + + + + + + + + + + + + D + Q + EN + dla + + + + + + + + + + + + + + + + + + + D + Q + EN + dlas + + + + + + + + + + + + + + D + Q + EN + dlan + + + + + + + + + + + + + + + + D + Q + EN + dlans + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + input + + + + + + output + + + + + + + constant + + + + + + + + + + + + + + hi:lo + + + hi:lo + + + + + + + + + hi:lo + + + hi:lo + + + + + + + + generic + + + + + + out0 + + + out1 + + + in0 + + + in1 + + + + \ No newline at end of file