diff --git a/src/cells/arith.mjs b/src/cells/arith.mjs
index 33bc254..5de1f1c 100644
--- a/src/cells/arith.mjs
+++ b/src/cells/arith.mjs
@@ -6,30 +6,56 @@ import bigInt from 'big-integer';
import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
-// Unary arithmetic operations
-export const Arith11 = Gate.define('Arith11', {
+// base class for arithmetic operations displayed with a circle
+export const Arith = Gate.define('Arith', {
size: { width: 40, height: 40 },
attrs: {
- 'circle.body': { r: 20, cx: 20, cy: 20 },
+ 'circle.body': { refR: 0.5, refCx: 0.5, refCy: 0.5 },
'text.oper': {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
+ refX: .5, refY: .5,
+ xAlignment: 'middle', yAlignment: 'middle',
+ fontSize: '12pt'
+ }
+ },
+ ports: {
+ groups: {
+ 'in': { position: { name: 'left', args: { dx: 10 } }, attrs: { 'line.wire': { x2: -30 }, 'circle.port': { refX: -30 } }, z: -1 },
+ 'out': { position: { name: 'right', args: { dx: -10 } }, attrs: { 'line.wire': { x2: 30 }, 'circle.port': { refX: 30 } }, z: -1 }
}
}
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in: 1, out: 1 };
- if (!args.signed) args.signed = false;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits.out }),
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.bits.in }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ markup: Gate.prototype.markup.concat([{
+ tagName: 'circle',
+ className: 'body'
+ }, {
+ tagName: 'text',
+ className: 'oper'
+ }
+ ]),
+ gateParams: Gate.prototype.gateParams.concat(['bits', 'signed']),
+ unsupportedPropChanges: Gate.prototype.unsupportedPropChanges.concat(['signed'])
+});
+
+// Unary arithmetic operations
+export const Arith11 = Arith.define('Arith11', {
+ /* default properties */
+ bits: { in: 1, out: 1 },
+ signed: false
+}, {
+ initialize: function() {
+ Arith.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: bits.in },
+ { id: 'out', group: 'out', dir: 'out', bits: bits.out }
+ ]);
+
+ this.on('change:bits', (_,bits) => {
+ this.setPortBits('in', bits.in);
+ this.setPortBits('out', bits.out);
+ });
},
operation: function(data) {
const bits = this.get('bits');
@@ -38,35 +64,31 @@ export const Arith11 = Gate.define('Arith11', {
return {
out: help.bigint2sig(this.arithop(help.sig2bigint(data.in, this.get('signed'))), bits.out)
};
- },
- gateParams: Gate.prototype.gateParams.concat(['bits', 'signed'])
+ }
});
// Binary arithmetic operations
-export const Arith21 = Gate.define('Arith21', {
- size: { width: 40, height: 40 },
- attrs: {
- 'circle.body': { r: 20, cx: 20, cy: 20 },
- 'text.oper': {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
- }
- }
+export const Arith21 = Arith.define('Arith21', {
+ /* default properties */
+ bits: { in1: 1, in2: 1, out: 1 },
+ signed: { in1: false, in2: false }
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in1: 1, in2: 1, out: 1 };
- if (!args.signed) args.signed = { in1: false, in2: false };
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits.out }),
- this.addWire(args, 'left', 0.3, { id: 'in1', dir: 'in', bits: args.bits.in1 }),
- this.addWire(args, 'left', 0.7, { id: 'in2', dir: 'in', bits: args.bits.in2 }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Arith.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in1', group: 'in', dir: 'in', bits: bits.in1 },
+ { id: 'in2', group: 'in', dir: 'in', bits: bits.in2 },
+ { id: 'out', group: 'out', dir: 'out', bits: bits.out }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in1', bits.in1);
+ this.setPortBits('in2', bits.in2);
+ this.setPortBits('out', bits.out);
+ });
},
operation: function(data) {
const bits = this.get('bits');
@@ -78,36 +100,32 @@ export const Arith21 = Gate.define('Arith21', {
help.sig2bigint(data.in1, sgn.in1 && sgn.in2),
help.sig2bigint(data.in2, sgn.in1 && sgn.in2)), bits.out)
};
- },
- gateParams: Gate.prototype.gateParams.concat(['bits', 'signed'])
+ }
});
// Bit shift operations
-export const Shift = Gate.define('Shift', {
- size: { width: 40, height: 40 },
- attrs: {
- 'circle.body': { r: 20, cx: 20, cy: 20 },
- 'text.oper': {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
- }
- }
+export const Shift = Arith.define('Shift', {
+ /* default properties */
+ bits: { in1: 1, in2: 1, out: 1 },
+ signed: { in1: false, in2: false, out: false },
+ fillx: false
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in1: 1, in2: 1, out: 1 };
- if (!args.signed) args.signed = { in1: false, in2: false, out: false };
- if (!args.fillx) args.fillx = false;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits.out }),
- this.addWire(args, 'left', 0.3, { id: 'in1', dir: 'in', bits: args.bits.in1 }),
- this.addWire(args, 'left', 0.7, { id: 'in2', dir: 'in', bits: args.bits.in2 }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Arith.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in1', group: 'in', dir: 'in', bits: bits.in1 },
+ { id: 'in2', group: 'in', dir: 'in', bits: bits.in2 },
+ { id: 'out', group: 'out', dir: 'out', bits: bits.out }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in1', bits.in1);
+ this.setPortBits('in2', bits.in2);
+ this.setPortBits('out', bits.out);
+ });
},
operation: function(data) {
const bits = this.get('bits');
@@ -125,34 +143,31 @@ export const Shift = Gate.define('Shift', {
: my_in.slice(am).concat(Vector3vl.make(am, fillx ? 0 : sgn.out ? my_in.get(my_in.bits-1) : -1));
return { out: out.slice(0, bits.out) };
},
- gateParams: Gate.prototype.gateParams.concat(['bits', 'signed', 'fillx'])
+ gateParams: Arith.prototype.gateParams.concat(['fillx']),
+ unsupportedPropChanges: Arith.prototype.unsupportedPropChanges.concat(['fillx'])
});
// Comparison operations
-export const Compare = Gate.define('Compare', {
- size: { width: 40, height: 40 },
- attrs: {
- 'circle.body': { r: 20, cx: 20, cy: 20 },
- 'text.oper': {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
- }
- }
+export const Compare = Arith.define('Compare', {
+ /* default properties */
+ bits: { in1: 1, in2: 1 },
+ signed: { in1: false, in2: false }
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in1: 1, in2: 1 };
- if (!args.signed) args.signed = { in1: false, in2: false };
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: 1 }),
- this.addWire(args, 'left', 0.3, { id: 'in1', dir: 'in', bits: args.bits.in1 }),
- this.addWire(args, 'left', 0.7, { id: 'in2', dir: 'in', bits: args.bits.in2 }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Arith.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in1', group: 'in', dir: 'in', bits: bits.in1 },
+ { id: 'in2', group: 'in', dir: 'in', bits: bits.in2 },
+ { id: 'out', group: 'out', dir: 'out', bits: 1 }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in1', bits.in1);
+ this.setPortBits('in2', bits.in2);
+ });
},
operation: function(data) {
const bits = this.get('bits');
@@ -164,35 +179,11 @@ export const Compare = Gate.define('Compare', {
help.sig2bigint(data.in1, sgn.in1 && sgn.in2),
help.sig2bigint(data.in2, sgn.in1 && sgn.in2)))
};
- },
- gateParams: Gate.prototype.gateParams.concat(['bits', 'signed'])
+ }
});
-export const EqCompare = Gate.define('EqCompare', {
- size: { width: 40, height: 40 },
- attrs: {
- 'circle.body': { r: 20, cx: 20, cy: 20 },
- 'text.oper': {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
- }
- }
-}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in1: 1, in2: 1 };
- if (!args.signed) args.signed = { in1: false, in2: false };
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: 1 }),
- this.addWire(args, 'left', 0.3, { id: 'in1', dir: 'in', bits: args.bits.in1 }),
- this.addWire(args, 'left', 0.7, { id: 'in2', dir: 'in', bits: args.bits.in2 }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
- },
+// Equality operations
+export const EqCompare = Compare.define('EqCompare', {}, {
operation: function(data) {
const bits = this.get('bits');
const sgn = this.get('signed');
@@ -203,8 +194,7 @@ export const EqCompare = Gate.define('EqCompare', {
return {
out: this.bincomp(in1, in2)
};
- },
- gateParams: Gate.prototype.gateParams.concat(['bits', 'signed'])
+ }
});
// Negation
diff --git a/src/cells/base.mjs b/src/cells/base.mjs
index 175332e..cc9a9fe 100644
--- a/src/cells/base.mjs
+++ b/src/cells/base.mjs
@@ -5,91 +5,183 @@ import bigInt from 'big-integer';
import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
+export const portGroupAttrs = {
+ 'line.wire': {
+ stroke: '#4B4F6A',
+ x1: 0, y1: 0,
+ x2: undefined, y2: 0
+ },
+ 'circle.port': {
+ magnet: undefined,
+ r: 7,
+ stroke: 'black',
+ fill: 'white',
+ strokeWidth: 2,
+ strokeOpacity: 0.5,
+ jointSelector: '.port'
+ },
+ 'text.bits': {
+ ref: 'circle.port',
+ fill: 'black',
+ fontSize: '7pt'
+ },
+ 'text.iolabel': {
+ yAlignment: 'middle',
+ fill: 'black',
+ fontSize: '8pt'
+ },
+ 'path.decor': {
+ stroke: 'black',
+ fill: 'transparent',
+ d: undefined
+ }
+};
+
// Common base class for gate models
export const Gate = joint.shapes.basic.Generic.define('Gate', {
+ /* default properties */
+ propagation: 1,
+ label: '',
+
size: { width: 80, height: 30 },
inputSignals: {},
outputSignals: {},
- propagation: 1,
attrs: {
'.': { magnet: false },
- 'rect.body': { width: 80, height: 30 },
- 'circle.port': { r: 7, stroke: 'black', fill: 'transparent', 'stroke-width': 2 },
- 'text.label': {
- text: '', 'ref-x': 0.5, 'ref-dy': 2, 'text-anchor': 'middle',
+ '.body': { stroke: 'black', strokeWidth: 2 },
+ 'text': {
+ fontSize: '8pt',
fill: 'black'
},
- 'text.bits': {
- fill: 'black'
+ 'text.label': {
+ refX: .5, refDy: 3,
+ xAlignment: 'middle'
+ }
+ },
+ ports: {
+ groups: {
+ 'in': {
+ position: 'left',
+ attrs: _.merge({}, portGroupAttrs, { 'line.wire': { x2: -20 }, 'circle.port': { magnet: 'passive', refX: -20 }, 'text.bits': { refDx: 3, refY: -4, textAnchor: 'start' }, 'text.iolabel': { refX: 5, textAnchor: 'start' } })
+ },
+ 'out': {
+ position: 'right',
+ attrs: _.merge({}, portGroupAttrs, { 'line.wire': { x2: 20 }, 'circle.port': { magnet: true, refX: 20 }, 'text.bits': { refX: -3, refY: -4, textAnchor: 'end' }, 'text.iolabel': { refX: -5, textAnchor: 'end' } })
+ }
}
}
}, {
operation: function() {
return {};
},
- constructor: function(args) {
- if ('label' in args) _.set(args, ['attrs', 'text.label', 'text'], args.label);
- joint.shapes.basic.Generic.prototype.constructor.apply(this, arguments);
- },
initialize: function() {
joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments);
- this.listenTo(this, 'change:size', (model, size) => this.attr('rect.body', size));
+
+ this.bindAttrToProp('text.label/text', 'label');
+ if (this.unsupportedPropChanges.length > 0) {
+ this.on(this.unsupportedPropChanges.map(prop => 'change:'+prop).join(' '), function(model, _, opt) {
+ if (opt.init) return;
+
+ if (opt.propertyPath)
+ console.warn('Beta property change support: "' + opt.propertyPath + '" changes on ' + model.prop('type') + ' are currently not reflected.');
+ else
+ console.warn('Beta property change support: changes on ' + model.prop('type') + ' are currently not reflected. Also consider using Cell.prop() instead of Model.set().');
+ });
+ }
},
- addWire: function(args, side, loc, port) {
- const vert = side == 'top';
- const wire_args = {
- d: 'M 0 0 L ' + (vert ? '0 40' : side == 'left' ? '40 0' : '-40 0')
- };
- const circle_args = {
- magnet: port.dir == 'out' ? true : 'passive',
- port: port
- };
- const ref_args = {};
- ref_args[vert ? 'ref-x' : 'ref-y'] = loc;
- if (side == 'left') {
- ref_args['ref-x'] = -20;
- } else if (side == 'right') {
- ref_args['ref-dx'] = 20;
- } else if (side == 'top') {
- ref_args['ref-y'] = -10; // currently mux only
- } else console.assert(false);
- _.assign(wire_args, ref_args);
- _.assign(circle_args, ref_args);
- _.set(args, ['attrs', 'path.wire.port_' + port.id], wire_args);
- _.set(args, ['attrs', 'circle.port_' + port.id], circle_args);
- let markup = '';
-
- markup += '';
- const bits_args = {
- text: port.bits > 1 ? port.bits : "",
- ref: 'circle.port_' + port.id
- };
- if (vert) {
- // TODO
- } else {
- bits_args['ref-y'] = -3;
- bits_args['text-anchor'] = 'middle';
+ bindAttrToProp: function(attr, prop) {
+ this.attr(attr, this.prop(prop));
+ this.on('change:' + prop, (_, val) => this.attr(attr, val));
+ },
+ setPortBits: function(port, bits) {
+ this.portProp(port, 'bits', bits);
+ this.portProp(port, 'attrs/text.bits/text', bits > 1 ? bits : '');
+ this.resetPortSignals(port, bits);
+ //todo: handle connected wires
+ console.warn('Beta property change support: Connected wires are currently not rechecked for connection');
+ },
+ resetPortSignals: function(port, bits) {
+ const signame = this.portProp(port, 'dir') === 'in' ? 'inputSignals' : 'outputSignals';
+ this.prop([signame, this.portProp(port, 'id')], Vector3vl.xes(bits));
+ },
+ removePortSignals: function(port) {
+ const signame = port.dir === 'in' ? 'inputSignals' : 'outputSignals';
+ this.removeProp([signame, port.id]);
+ },
+ addPort: function(port, opt = {}) {
+ joint.shapes.basic.Generic.prototype.addPort.apply(this, arguments);
+ this.resetPortSignals(port.id, port.bits);
+ if (opt.labelled) {
+ this.portProp(port, 'attrs/text.iolabel/text', 'portlabel' in port ? port.portlabel : port.id);
+ if (port.polarity === false)
+ this.portProp(port, 'attrs/text.iolabel/text-decoration', 'overline');
+ if (port.decor) {
+ console.assert(port.group == 'in');
+ this.portProp(port, 'attrs/text.iolabel/refX', 10);
+ }
}
- if (side == 'left') {
- bits_args['ref-dx'] = 6;
- } else if (side == 'right') {
- bits_args['ref-x'] = -6;
- } else if (side == 'top') {
- bits_args['ref-y'] = 6;
- } else console.assert(false);
- _.set(args, ['attrs', 'text.bits.port_' + port.id], bits_args);
-
- const signame = port.dir == 'in' ? 'inputSignals' : 'outputSignals';
- if (_.get(args, [signame, port.id]) === undefined) {
- _.set(args, [signame, port.id], Vector3vl.xes(port.bits));
+ if (port.decor) {
+ this.portProp(port, 'attrs/path.decor/d', port.decor);
+ }
+ },
+ addPorts: function(ports, opt) {
+ ports.forEach((port) => this.addPort(port, opt), this);
+ },
+ removePort: function(port, opt) {
+ joint.shapes.basic.Generic.prototype.removePort.apply(this, arguments);
+ this.removePortSignals(port.id !== undefined ? port.id : port);
+ },
+ removePorts: function(ports, opt) {
+ ports.forEach((port) => this.removePort(port, opt), this);
+ },
+ getStackedPosition: function(opt) {
+ return function(portsArgs, elBBox) {
+ // ports stacked from top to bottom or left to right
+ const side = opt.side || 'left';
+ const step = opt.step || 16;
+ const offset = opt.offset || 12;
+ const x = side == 'left' ? elBBox.topLeft().x : side == 'right' ? elBBox.topRight().x : undefined;
+ const y = side == 'top' ? elBBox.topLeft().y : side == 'bottom' ? elBBox.bottomRight().y : undefined;
+ if (x !== undefined) {
+ return _.map(portsArgs, function(portArgs, index) {
+ index += portArgs.idxOffset || 0;
+ return joint.g.Point({ x: x, y: index*step + offset });
+ });
+ } else {
+ return _.map(portsArgs, function(portArgs, index) {
+ index += portArgs.idxOffset || 0;
+ return joint.g.Point({ x: index*step + offset, y: y });
+ });
+ }
}
- return '' + markup + '';
},
+ portMarkup: [{
+ tagName: 'line',
+ className: 'wire'
+ }, {
+ tagName: 'circle',
+ className: 'port'
+ }, {
+ tagName: 'text',
+ className: 'bits'
+ }, {
+ tagName: 'text',
+ className: 'iolabel'
+ }, {
+ tagName: 'path',
+ className: 'decor'
+ }],
+ //portLabelMarkup: null, //todo: see https://github.com/clientIO/joint/issues/1278
+ markup: [{
+ tagName: 'text',
+ className: 'label'
+ }],
getGateParams: function(layout) {
return _.cloneDeep(_.pick(this.attributes, this.gateParams.concat(layout ? this.gateLayoutParams : [])));
},
gateParams: ['label', 'type', 'propagation'],
- gateLayoutParams: ['position']
+ gateLayoutParams: ['position'],
+ unsupportedPropChanges: []
});
export const GateView = joint.dia.ElementView.extend({
@@ -103,20 +195,19 @@ export const GateView = joint.dia.ElementView.extend({
confirmUpdate(flags) {
if (this.hasFlag(flags, 'flag:inputSignals')) {
this.updatePortSignals('in', this.model.get('inputSignals'));
- };
+ }
if (this.hasFlag(flags, 'flag:outputSignals')) {
this.updatePortSignals('out', this.model.get('outputSignals'));
- };
+ }
joint.dia.ElementView.prototype.confirmUpdate.apply(this, arguments);
},
updatePortSignals(dir, signal) {
- for (const port of Object.values(this.model.ports)) {
+ for (const port of this.model.getPorts()) {
if (port.dir !== dir) continue;
- let classes = ['port', port.dir, 'port_' + port.id];
- if (signal[port.id].isHigh) classes.push('live');
- else if (signal[port.id].isLow) classes.push('low');
- else if (signal[port.id].isDefined) classes.push('defined');
- this.$('circle.port_' + port.id).attr('class', classes.join(' '));
+ const portel = this.el.querySelector('[port='+port.id+']');
+ portel.classList.toggle('live', signal[port.id].isHigh);
+ portel.classList.toggle('low', signal[port.id].isLow);
+ portel.classList.toggle('defined', signal[port.id].isDefined);
}
},
render() {
@@ -321,53 +412,67 @@ export const WireView = joint.dia.LinkView.extend({
export const Box = Gate.define('Box', {
attrs: {
- 'text.iolabel': { fill: 'black', 'dominant-baseline': 'ideographic' },
- 'path.decor': { stroke: 'black', fill: 'transparent' }
+ 'rect.body': { refWidth: 1, refHeight: 1 },
+ '.tooltip': { refX: 0, refY: -30, height: 30 }
}
}, {
- addLabelledWire: function(args, lblmarkup, side, loc, port) {
- console.assert(side == 'left' || side == 'right');
- const ret = this.addWire(args, side, loc, port);
- lblmarkup.push('');
- const textattrs = {
- 'ref-y': loc, 'text-anchor': side == 'left' ? 'start' : 'end',
- text: 'label' in port ? port.label : port.id
- };
- const dist = port.clock ? 10 : 5;
- if (side == 'left') textattrs['ref-x'] = dist;
- else if (side == 'right') textattrs['ref-dx'] = -dist;
- if (port.polarity === false) textattrs['text-decoration'] = 'overline';
- if (port.clock) {
- console.assert(side == 'left');
- let vpath = [
- [0, -6],
- [6, 0],
- [0, 6]
- ];
- const path = 'M' + vpath.map(l => l.join(' ')).join(' L');
- lblmarkup.push('');
- _.set(args, ['attrs', 'path.decor.port_' + port.id], {
- 'ref-x': 0, 'ref-y': loc
- });
+ initialize: function(args) {
+ Gate.prototype.initialize.apply(this, arguments);
+ this.on('change:size', (_, size) => {
+ if (size.width > this.tooltipMinWidth) {
+ this.attr('.tooltip', { refWidth: 1, width: null });
+ } else {
+ this.attr('.tooltip', { refWidth: null, width: this.tooltipMinWidth });
+ }
+ });
+ this.trigger('change:size', this, this.prop('size'));
+ },
+ markup: Gate.prototype.markup.concat([{
+ tagName: 'rect',
+ className: 'body'
}
- _.set(args, ['attrs', 'text.iolabel.port_' + port.id], textattrs);
- return ret;
- }
+ ]),
+ markupZoom: [{
+ tagName: 'foreignObject',
+ className: 'tooltip',
+ children: [{
+ tagName: 'body',
+ namespaceURI: 'http://www.w3.org/1999/xhtml',
+ children: [{
+ tagName: 'a',
+ className: 'zoom',
+ textContent: '🔍',
+ style: { cursor: 'pointer' }
+ }]
+ }]
+ }],
+ tooltipMinWidth: 20,
+ decorClock: 'M' + [
+ [0, -6],
+ [6, 0],
+ [0, 6]
+ ].map(l => l.join(' ')).join(' L')
});
+// base class for gates displayed as a box
export const BoxView = GateView.extend({
+ autoResizeBox: false,
render: function() {
GateView.prototype.render.apply(this, arguments);
- if (this.model.get('box_resized')) return;
- this.model.set('box_resized', true);
- const labels = Array.from(this.el.querySelectorAll('text.iolabel'));
- const leftlabels = labels.filter(x => x.classList.contains('iolabel_left'));
- const rightlabels = labels.filter(x => x.classList.contains('iolabel_right'));
+ if (this.autoResizeBox) {
+ if (this.model.get('box_resized')) return;
+ this.model.set('box_resized', true);
+ this.model.prop('size/width', this.calculateBoxWidth());
+ }
+ },
+ calculateBoxWidth: function() {
+ const leftlabels = Array.from(this.el.querySelectorAll('[port-group=in] > text.iolabel'));
+ const rightlabels = Array.from(this.el.querySelectorAll('[port-group=out] > text.iolabel'));
const leftwidth = Math.max(...leftlabels.map(x => x.getBBox().width));
const rightwidth = Math.max(...rightlabels.map(x => x.getBBox().width));
const fixup = x => x == -Infinity ? -5 : x;
const width = fixup(leftwidth) + fixup(rightwidth) + 25;
- this.model.set('size', _.set(_.clone(this.model.get('size')), 'width', width));
+ return width;
}
});
diff --git a/src/cells/bus.mjs b/src/cells/bus.mjs
index d302673..5692592 100644
--- a/src/cells/bus.mjs
+++ b/src/cells/bus.mjs
@@ -1,40 +1,59 @@
"use strict";
import * as joint from 'jointjs';
-import { Gate, GateView, Box, BoxView } from './base';
+import { Box, BoxView } from './base';
import bigInt from 'big-integer';
import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
// Bit extending
-export const BitExtend = Gate.define('BitExtend', {
+export const BitExtend = Box.define('BitExtend', {
+ /* default properties */
+ extend: { input: 1, output: 1 },
propagation: 0,
+
attrs: {
"text.value": {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle',
- 'text-anchor': 'middle',
- 'font-size': '14px'
+ refX: .5, refY: .5,
+ xAlignment: 'middle', yAlignment: 'middle'
}
}
}, {
- constructor: function(args) {
- console.assert(args.extend.input <= args.extend.output);
- this.markup = [
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.extend.input}),
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.extend.output}),
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Box.prototype.initialize.apply(this, arguments);
+
+ const extend = this.prop('extend');
+
+ console.assert(extend.input <= extend.output);
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: extend.input },
+ { id: 'out', group: 'out', dir: 'out', bits: extend.output }
+ ]);
+
+ this.on('change:extend', (_, extend) => {
+ this.setPortBits('in', extend.input);
+ this.setPortBits('out', extend.output);
+ });
},
operation: function(data) {
const ex = this.get('extend');
return { out: data.in.concat(Vector3vl.make(ex.output - ex.input, this.extbit(data.in))) };
},
- gateParams: Gate.prototype.gateParams.concat(['extend'])
+ markup: Box.prototype.markup.concat([{
+ tagName: 'text',
+ className: 'value'
+ }
+ ]),
+ gateParams: Box.prototype.gateParams.concat(['extend'])
+});
+export const BitExtendView = BoxView.extend({
+ autoResizeBox: true,
+ calculateBoxWidth: function() {
+ const text = this.el.querySelector('text.value');
+ return text.getBBox().width + 10;
+ }
});
-export const BitExtendView = GateView;
export const ZeroExtend = BitExtend.define('ZeroExtend', {
attrs: {
@@ -60,59 +79,64 @@ export const SignExtendView = BitExtendView;
// Bus slicing
export const BusSlice = Box.define('BusSlice', {
+ /* default properties */
+ slice: { first: 0, count: 1, total: 2 },
propagation: 0,
- size: { width: 40, height: 24 },
+
+ size: { width: 40, height: 24 }
}, {
- constructor: function(args) {
- const lblmarkup = [];
- const markup = [];
- args.bits = 0;
- const val = args.slice.count == 1 ? args.slice.first :
- args.slice.first + "-" + (args.slice.first + args.slice.count - 1);
- this.markup = [
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.slice.total}),
- this.addLabelledWire(args, lblmarkup, 'right', 0.5, { id: 'out', dir: 'out', bits: args.slice.count, label: val}),
- '',
- lblmarkup.join(''),
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Box.prototype.initialize.apply(this, arguments);
+
+ const slice = this.prop('slice');
+
+ const val = slice.count == 1 ? slice.first :
+ slice.first + "-" + (slice.first + slice.count - 1);
+
+ this.addPort({ id: 'in', group: 'in', dir: 'in', bits: slice.total });
+ this.addPort({ id: 'out', group: 'out', dir: 'out', bits: slice.count, portlabel: val }, { labelled: true });
},
operation: function(data) {
const s = this.get('slice');
return { out: data.in.slice(s.first, s.first + s.count) };
},
- gateParams: Gate.prototype.gateParams.concat(['slice'])
+ gateParams: Box.prototype.gateParams.concat(['slice'])
+});
+export const BusSliceView = BoxView.extend({
+ autoResizeBox: true
});
-export const BusSliceView = BoxView;
// Bus grouping
export const BusRegroup = Box.define('BusRegroup', {
- propagation: 0,
+ /* default properties */
+ groups: [1],
+ propagation: 0
}, {
- constructor: function(args) {
- const markup = [];
- const lblmarkup = [];
- args.bits = 0;
- const size = { width: 40, height: args.groups.length*16+8 };
- args.size = size;
- for (const [num, gbits] of args.groups.entries()) {
- const y = num*16+12;
- const lbl = args.bits + (gbits > 1 ? '-' + (args.bits + gbits - 1) : '');
- args.bits += gbits;
- markup.push(this.addLabelledWire(args, lblmarkup, this.group_dir == 'out' ? 'right' : 'left', y,
- { id: this.group_dir + num, dir: this.group_dir, bits: gbits, label: lbl }));
+ initialize: function() {
+ Box.prototype.initialize.apply(this, arguments);
+
+ var bits = 0;
+ const groups = this.prop('groups');
+
+ const size = { width: 40, height: groups.length*16+8 };
+ this.prop('size', size);
+
+ for (const [num, gbits] of groups.entries()) {
+ const lbl = bits + (gbits > 1 ? '-' + (bits + gbits - 1) : '');
+ bits += gbits;
+ this.addPort({ id: this.group_dir + num, group: this.group_dir, dir: this.group_dir, bits: gbits, portlabel: lbl }, { labelled: true });
}
+ this.prop('bits', bits);
+
const contra = this.group_dir == 'out' ? 'in' : 'out';
- markup.push(this.addWire(args, this.group_dir == 'out' ? 'left' : 'right', 0.5,
- { id: contra, dir: contra, bits: args.bits }));
- markup.push('');
- markup.push(lblmarkup.join(''));
- this.markup = markup.join('');
- Gate.prototype.constructor.apply(this, arguments);
+ this.addPort({ id: contra, group: contra, dir: contra, bits: bits });
},
- gateParams: Gate.prototype.gateParams.concat(['groups'])
+ gateParams: Box.prototype.gateParams.concat(['groups']),
+ unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['groups'])
+});
+export const BusRegroupView = BoxView.extend({
+ autoResizeBox: true
});
-export const BusRegroupView = BoxView;
export const BusGroup = BusRegroup.define('BusGroup', {
}, {
diff --git a/src/cells/dff.mjs b/src/cells/dff.mjs
index ad5cc0e..16d6c91 100644
--- a/src/cells/dff.mjs
+++ b/src/cells/dff.mjs
@@ -8,32 +8,54 @@ import { Vector3vl } from '3vl';
// D flip-flops
export const Dff = Box.define('Dff', {
+ /* default properties */
+ bits: 1,
+ polarity: { clock: true },
+ initial: 'x',
+
+ ports: {
+ groups: {
+ 'in': {
+ position: Box.prototype.getStackedPosition({ side: 'left' })
+ },
+ 'out': {
+ position: Box.prototype.getStackedPosition({ side: 'right' })
+ }
+ }
+ }
}, {
- constructor: function(args) {
- _.defaults(args, { bits: 1, polarity: {}, initial: 'x' });
- if (!args.outputSignals)
- args.outputSignals = {
- out: Vector3vl.fromBin(args.initial, args.bits)
- };
- if ('arst' in args.polarity && !args.arst_value)
- args.arst_value = Array(args.bits).fill('0').join('');
- const markup = [];
- const lblmarkup = [];
- markup.push(this.addLabelledWire(args, lblmarkup, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits, label: 'Q' }));
- let num = 0;
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', (num++*16)+12, { id: 'in', dir: 'in', bits: args.bits, label: 'D' }));
- if ('clock' in args.polarity)
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', (num++*16)+12, { id: 'clk', dir: 'in', bits: 1, polarity: args.polarity.clock, clock: true }));
- if ('arst' in args.polarity)
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', (num++*16)+12, { id: 'arst', dir: 'in', bits: 1, polarity: args.polarity.arst }));
- if ('enable' in args.polarity)
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', (num++*16)+12, { id: 'en', dir: 'in', bits: 1, polarity: args.polarity.enable }));
- markup.push('');
- markup.push(lblmarkup.join(''));
- this.markup = markup.join('');
- const size = { width: 80, height: num*16+8 };
- args.size = size;
- Box.prototype.constructor.apply(this, arguments);
+ initialize: function() {
+ Box.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+ const initial = this.prop('initial');
+ const polarity = this.prop('polarity');
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: bits, portlabel: 'D' },
+ { id: 'out', group: 'out', dir: 'out', bits: bits, portlabel: 'Q' }
+ ], { labelled: true });
+ this.prop('outputSignals/out', Vector3vl.fromBin(initial, bits));
+
+ if ('arst' in polarity && this.prop('arst_value'))
+ this.prop('arst_value', Array(bits).fill('0').join(''));
+
+ let num = 1;
+ if ('clock' in polarity) {
+ num++;
+ this.addPort({ id: 'clk', group: 'in', dir: 'in', bits: 1, polarity: polarity.clock, decor: Box.prototype.decorClock }, { labelled: true });
+ }
+ if ('arst' in polarity) {
+ num++;
+ this.addPort({ id: 'arst', group: 'in', dir: 'in', bits: 1, polarity: polarity.arst }, { labelled: true });
+ }
+ if ('enable' in polarity) {
+ num++;
+ this.addPort({ id: 'en', group: 'in', dir: 'in', bits: 1, polarity: polarity.enable }, { labelled: true });
+ }
+
+ this.prop('size', { width: 80, height: num*16+8 });
+
this.last_clk = 0;
},
operation: function(data) {
@@ -55,7 +77,10 @@ export const Dff = Box.define('Dff', {
return this.get('outputSignals');
} else return { out: data.in };
},
- gateParams: Box.prototype.gateParams.concat(['polarity', 'bits'])
+ gateParams: Box.prototype.gateParams.concat(['polarity', 'bits', 'initial']),
+ unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['polarity', 'bits', 'initial'])
+});
+export const DffView = BoxView.extend({
+ autoResizeBox: true
});
-export const DffView = BoxView;
diff --git a/src/cells/fsm.mjs b/src/cells/fsm.mjs
index 4fa06bd..f6c88e0 100644
--- a/src/cells/fsm.mjs
+++ b/src/cells/fsm.mjs
@@ -11,18 +11,76 @@ import dagre from 'dagre';
import graphlib from 'graphlib';
export const FSM = Box.define('FSM', {
+ /* default properties */
+ bits: { in: 1, out: 1},
+ polarity: { clock: true },
+ init_state: 0,
+ states: 1,
+ trans_table: [],
+
size: { width: 80, height: 3*16+8 },
- attrs: {
- '.tooltip': {
- 'ref-x': 0, 'ref-y': -30,
- width: 80, height: 30
- },
+ ports: {
+ groups: {
+ 'in': {
+ position: Box.prototype.getStackedPosition({ side: 'left' })
+ },
+ 'out': {
+ position: Box.prototype.getStackedPosition({ side: 'right' })
+ }
+ }
}
}, {
initialize: function() {
- this.listenTo(this, 'change:size', (model, size) => {
- this.attr('.tooltip/width', size.width)
- });
+ Box.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+ const polarity = this.prop('polarity');
+
+ const init_state = this.prop('init_state');
+ var current_state = this.prop('current_state');
+ if (current_state === undefined) {
+ current_state = init_state;
+ this.prop('current_state', current_state);
+ }
+ const states = this.prop('states');
+ const trans_table = this.prop('trans_table');
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: bits.in },
+ { id: 'clk', group: 'in', dir: 'in', bits: 1, polarity: polarity.clock, decor: Box.prototype.decorClock },
+ { id: 'arst', group: 'in', dir: 'in', bits: 1, polarity: polarity.arst },
+ { id: 'out', group: 'out', dir: 'out', bits: bits.out },
+ ], { labelled: true });
+
+ this.fsmgraph = new joint.dia.Graph;
+ const statenodes = [];
+ for (let n = 0; n < states; n++) {
+ const node = new joint.shapes.standard.Circle({stateNo: n, id: 'state' + n, isInit: n == init_state});
+ node.attr('label/text', String(n));
+ node.resize(100,50);
+ node.addTo(this.fsmgraph);
+ statenodes.push(node);
+ }
+ for (const tr of trans_table) {
+ const trans = new joint.shapes.standard.Link({
+ ctrlIn: Vector3vl.fromBin(tr.ctrl_in, bits.in),
+ ctrlOut: Vector3vl.fromBin(tr.ctrl_out, bits.out)
+ });
+ trans.appendLabel({
+ attrs: {
+ text: {
+ text: trans.get('ctrlIn').toBin() + '/' + trans.get('ctrlOut').toBin()
+ }
+ }
+ });
+ trans.source({ id: 'state' + tr.state_in });
+ trans.target({ id: 'state' + tr.state_out });
+ trans.addTo(this.fsmgraph);
+ }
+
+ this.last_clk = 0;
+ //args.next_trans = undefined; //todo: needed?
+
this.listenTo(this, 'change:current_state', (model, state) => {
const pstate = model.previous('current_state');
this.fsmgraph.getCell('state' + pstate).removeAttr('body/class');
@@ -46,52 +104,6 @@ export const FSM = Box.define('FSM', {
});
}
});
- Box.prototype.initialize.apply(this, arguments);
- },
- constructor: function(args) {
- if (!args.init_state) args.init_state = 0;
- if (!('current_state' in args)) args.current_state = args.init_state;
- args.next_trans = undefined;
- const markup = [];
- const lblmarkup = [];
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', 16+12, { id: 'clk', dir: 'in', bits: 1, polarity: args.polarity.clock, clock: true }));
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', 2*16+12, { id: 'arst', dir: 'in', bits: 1, polarity: args.polarity.arst }));
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', 12, { id: 'in', dir: 'in', bits: args.bits.in }));
- markup.push(this.addLabelledWire(args, lblmarkup, 'right', 12, { id: 'out', dir: 'out', bits: args.bits.out }));
- markup.push('');
- markup.push(lblmarkup.join(''));
- markup.push(['',
- '',
- '🔍',
- ''].join(''));
- this.markup = markup.join('');
- this.fsmgraph = new joint.dia.Graph;
- const statenodes = [];
- for (let n = 0; n < args.states; n++) {
- const node = new joint.shapes.standard.Circle({stateNo: n, id: 'state' + n, isInit: n == args.init_state});
- node.attr('label/text', String(n));
- node.resize(100,50);
- node.addTo(this.fsmgraph);
- statenodes.push(node);
- }
- for (const tr of args.trans_table) {
- const trans = new joint.shapes.standard.Link({
- ctrlIn: Vector3vl.fromBin(tr.ctrl_in, args.bits.in),
- ctrlOut: Vector3vl.fromBin(tr.ctrl_out, args.bits.out)
- });
- trans.appendLabel({
- attrs: {
- text: {
- text: trans.get('ctrlIn').toBin() + '/' + trans.get('ctrlOut').toBin()
- }
- }
- });
- trans.source({ id: 'state' + tr.state_in });
- trans.target({ id: 'state' + tr.state_out });
- trans.addTo(this.fsmgraph);
- }
- Box.prototype.constructor.apply(this, arguments);
- this.last_clk = 0;
},
operation: function(data) {
const bits = this.get('bits');
@@ -127,10 +139,13 @@ export const FSM = Box.define('FSM', {
return { out: trans.get('ctrlOut') };
}
},
- gateParams: Box.prototype.gateParams.concat(['bits', 'polarity', 'wirename', 'states', 'init_state', 'trans_table'])
+ markup: Box.prototype.markup.concat(Box.prototype.markupZoom),
+ gateParams: Box.prototype.gateParams.concat(['bits', 'polarity', 'states', 'init_state', 'trans_table']),
+ unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['bits', 'polarity', 'states', 'init_state', 'trans_table'])
});
export const FSMView = BoxView.extend({
+ autoResizeBox: true,
events: {
"click foreignObject.tooltip": "stopprop",
"mousedown foreignObject.tooltip": "stopprop",
diff --git a/src/cells/gates.mjs b/src/cells/gates.mjs
index a727971..b8339aa 100644
--- a/src/cells/gates.mjs
+++ b/src/cells/gates.mjs
@@ -6,64 +6,84 @@ import bigInt from 'big-integer';
import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
-// Single-input gate model
-export const Gate11 = Gate.define('Gate11', {
+// base class for gates displayed using an external svg image
+export const GateSVG = Gate.define('GateSVG', {
+ /* default properties */
+ bits: 1,
+
size: { width: 60, height: 40 },
attrs: {
- '.body': { width: 60, height: 40 }
- }
-}, {
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits }),
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.bits }),
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ 'image.body': { refWidth: 1, refHeight: 1 }
},
+ ports: {
+ groups: {
+ 'in': { position: { name: 'left', args: { dx: 20 } }, attrs: { 'line.wire': { x2: -35 }, 'circle.port': { refX: -35 } }, z: -1 },
+ 'out': { position: { name: 'right', args: { dx: -20 } }, attrs: { 'line.wire': { x2: 35 }, 'circle.port': { refX: 35 } }, z: -1 }
+ }
+ }
+}, {
+ markup: Gate.prototype.markup.concat([{
+ tagName: 'image',
+ className: 'body'
+ }
+ ]),
gateParams: Gate.prototype.gateParams.concat(['bits'])
});
+// Single-input gate model
+export const Gate11 = GateSVG.define('Gate11', {}, {
+ initialize: function() {
+ GateSVG.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: bits },
+ { id: 'out', group: 'out', dir: 'out', bits: bits }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in', bits);
+ this.setPortBits('out', bits);
+ });
+ }
+});
+
// Two-input gate model
-export const Gate21 = Gate.define('Gate21', {
- size: { width: 60, height: 40 },
- attrs: {
- '.body': { width: 60, height: 40 }
+export const Gate21 = GateSVG.define('Gate21', {}, {
+ initialize: function() {
+ GateSVG.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+ this.addPorts([
+ { id: 'in1', group: 'in', dir: 'in', bits: bits },
+ { id: 'in2', group: 'in', dir: 'in', bits: bits },
+ { id: 'out', group: 'out', dir: 'out', bits: bits }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in1', bits);
+ this.setPortBits('in2', bits);
+ this.setPortBits('out', bits);
+ });
}
-}, {
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits }),
- this.addWire(args, 'left', 0.3, { id: 'in1', dir: 'in', bits: args.bits }),
- this.addWire(args, 'left', 0.7, { id: 'in2', dir: 'in', bits: args.bits }),
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
- },
- gateParams: Gate.prototype.gateParams.concat(['bits'])
});
// Reducing gate model
-export const GateReduce = Gate.define('GateReduce', {
- size: { width: 60, height: 40 },
- attrs: {
- '.body': { width: 60, height: 40 }
+export const GateReduce = GateSVG.define('GateReduce', {}, {
+ initialize: function() {
+ GateSVG.prototype.initialize.apply(this, arguments);
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'in', group: 'in', dir: 'in', bits: bits },
+ { id: 'out', group: 'out', dir: 'out', bits: 1 }
+ ]);
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in', bits);
+ });
}
-}, {
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: 1 }),
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.bits }),
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
- },
});
// Repeater (buffer) gate model
diff --git a/src/cells/io.mjs b/src/cells/io.mjs
index 91cd18d..5eb6c28 100644
--- a/src/cells/io.mjs
+++ b/src/cells/io.mjs
@@ -1,7 +1,7 @@
"use strict";
import * as joint from 'jointjs';
-import { Gate, GateView } from './base';
+import { Box, BoxView } from './base';
import _ from 'lodash';
import $ from 'jquery';
import bigInt from 'big-integer';
@@ -9,35 +9,46 @@ import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
// Things with numbers
-export const NumBase = Gate.define('NumBase', {
- numbase: 'hex',
- attrs: {
- '.tooltip': {
- 'ref-x': 0, 'ref-y': -30,
- width: 80, height: 30
- },
- }
+export const NumBase = Box.define('NumBase', {
+ /* default properties */
+ numbase: 'hex'
}, {
- initialize: function(args) {
- this.listenTo(this, 'change:size', (x, size) => {
- this.attr('.tooltip/width', Math.max(size.width, 50))
- });
- Gate.prototype.initialize.apply(this, arguments);
- },
- numbaseMarkup: [
- // requiredExtensions="http://www.w3.org/1999/xhtml" not supported by Chrome
- '',
- '',
- '',
- ''].join(''),
- gateParams: Gate.prototype.gateParams.concat(['numbase'])
+ tooltipMinWidth: 55,
+ markup: Box.prototype.markup.concat([{
+ tagName: 'foreignObject',
+ className: 'tooltip',
+ children: [{
+ tagName: 'body',
+ //todo: investigate about namespaceURI on other browsers, works on Firefox only if attribute set, but not added to SVG code, what happens with e.g. Chrome here?
+ namespaceURI: 'http://www.w3.org/1999/xhtml', // requiredExtensions="http://www.w3.org/1999/xhtml" not supported by Chrome
+ children: [{
+ tagName: 'select',
+ className: 'numbase',
+ children: [{
+ tagName: 'option',
+ attributes: { value: 'hex' },
+ textContent: 'hex'
+ }, {
+ tagName: 'option',
+ attributes: { value: 'dec' },
+ textContent: 'dec'
+ }, {
+ tagName: 'option',
+ attributes: { value: 'oct' },
+ textContent: 'oct'
+ }, {
+ tagName: 'option',
+ attributes: { value: 'bin' },
+ textContent: 'bin'
+ }]
+ }]
+ }]
+ }
+ ]),
+ gateParams: Box.prototype.gateParams.concat(['numbase'])
});
-export const NumBaseView = GateView.extend({
+export const NumBaseView = BoxView.extend({
+ autoResizeBox: true,
events: {
"click select.numbase": "stopprop",
"mousedown select.numbase": "stopprop",
@@ -46,94 +57,99 @@ export const NumBaseView = GateView.extend({
changeNumbase: function(evt) {
this.model.set('numbase', evt.target.value || 'bin');
},
- render: function() {
- GateView.prototype.render.apply(this, arguments);
- if (this.model.get('box_resized')) return;
- this.model.set('box_resized', true);
+ calculateBoxWidth: function() {
const testtext = document.createElementNS('http://www.w3.org/2000/svg', 'text');
$(testtext).text(Array(this.model.get('bits')).fill('0').join(''))
.attr('class', 'numvalue')
.appendTo(this.$el);
const width = testtext.getBBox().width + 20;
testtext.remove();
- this.model.set('size', _.set(_.clone(this.model.get('size')), 'width', width));
+ return width;
}
});
// Numeric display -- displays a number
export const NumDisplay = NumBase.define('NumDisplay', {
+ /* default properties */
bits: 1,
propagation: 0,
+
attrs: {
- '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 },
'text.value': {
- text: '',
- 'ref-x': .5, 'ref-y': .5,
- 'dominant-baseline': 'ideographic',
+ refX: .5, refY: .5,
+ yAlignment: 'middle'
},
}
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: args.bits }),
- '',
- this.numbaseMarkup,
- '',
- '',
- ].join('');
- NumBase.prototype.constructor.apply(this, arguments);
- },
initialize: function(args) {
NumBase.prototype.initialize.apply(this, arguments);
- const settext = () => {
- this.attr('text.value/text', help.sig2base(this.get('inputSignals').in, this.get('numbase')));
- }
+
+ const bits = this.prop('bits');
+
+ this.addPort({ id: 'in', group: 'in', dir: 'in', bits: bits });
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('in', bits);
+ });
+
+ const settext = () => this.attr('text.value/text', help.sig2base(this.get('inputSignals').in, this.get('numbase')));
settext();
- this.listenTo(this, 'change:inputSignals', settext);
- this.listenTo(this, 'change:numbase', settext);
+
+ this.on('change:inputSignals change:numbase', settext);
},
+ markup: NumBase.prototype.markup.concat([{
+ tagName: 'text',
+ className: 'value numvalue'
+ }
+ ]),
gateParams: NumBase.prototype.gateParams.concat(['bits'])
});
export const NumDisplayView = NumBaseView;
// Numeric entry -- parses a number from a text box
export const NumEntry = NumBase.define('NumEntry', {
+ /* default properties */
bits: 1,
propagation: 0,
buttonState: Vector3vl.xes(1),
+
attrs: {
- '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 },
'foreignObject.valinput': {
- 'ref-x': 5, 'ref-y': 0,
- width: 60, height: 30
+ refX: .5, refY: .5,
+ refWidth: -10, refHeight: -10,
+ xAlignment: 'middle', yAlignment: 'middle',
}
}
}, {
initialize: function(args) {
- this.listenTo(this, 'change:size', (x, size) => {
- this.attr('foreignObject.valinput/width', size.width - 10)
- });
NumBase.prototype.initialize.apply(this, arguments);
- },
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.bits }),
- '',
- this.numbaseMarkup,
- '',
- '',
- '',
- '',
- '',
- ].join('');
- args.buttonState = args.outputSignals.out;
- NumBase.prototype.constructor.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPort({ id: 'out', group: 'out', dir: 'out', bits: bits });
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits('out', bits);
+ });
+
+ this.prop('buttonState', this.prop('outputSignals/out'));
},
operation: function() {
return { out: this.get('buttonState') };
},
+ markup: NumBase.prototype.markup.concat([{
+ tagName: 'foreignObject',
+ className: 'valinput',
+ children: [{
+ tagName: 'body',
+ namespaceURI: 'http://www.w3.org/1999/xhtml',
+ children: [{
+ tagName: 'input',
+ attributes: { type: 'text' }
+ }]
+ }]
+ }
+ ]),
gateParams: NumBase.prototype.gateParams.concat(['bits'])
});
export const NumEntryView = NumBaseView.extend({
@@ -170,29 +186,29 @@ export const NumEntryView = NumBaseView.extend({
});
// Lamp model -- displays a single-bit input
-export const Lamp = Gate.define('Lamp', {
+export const Lamp = Box.define('Lamp', {
size: { width: 30, height: 30 },
attrs: {
- 'rect.body': { fill: 'white', stroke: 'black', 'stroke-width': 2, width: 30, height: 30 },
'.led': {
- 'ref-x': .5, 'ref-y': .5,
- r: 10
+ refX: .5, refY: .5,
+ refR: .35,
+ stroke: 'black'
}
}
}, {
- constructor: function(args) {
- this.markup = [
- this.addWire(args, 'left', 0.5, { id: 'in', dir: 'in', bits: 1 }),
- '',
- '',
- '',
- ].join('')
- Gate.prototype.constructor.apply(this, arguments);
- }
+ initialize: function(args) {
+ Box.prototype.initialize.apply(this, arguments);
+ this.addPort({ id: 'in', group: 'in', dir: 'in', bits: 1 });
+ },
+ markup: Box.prototype.markup.concat([{
+ tagName: 'circle',
+ className: 'led'
+ }
+ ])
});
-export const LampView = GateView.extend({
+export const LampView = BoxView.extend({
confirmUpdate(flags) {
- GateView.prototype.confirmUpdate.apply(this, arguments);
+ BoxView.prototype.confirmUpdate.apply(this, arguments);
if (this.hasFlag(flags, 'flag:inputSignals')) {
this.updateLamp(this.model.get('inputSignals'));
};
@@ -202,48 +218,50 @@ export const LampView = GateView.extend({
this.$(".led").toggleClass('low', signal.in.isLow);
},
render() {
- GateView.prototype.render.apply(this, arguments);
+ BoxView.prototype.render.apply(this, arguments);
this.updateLamp(this.model.get('inputSignals'));
}
});
// Button model -- single-bit clickable input
-export const Button = Gate.define('Button', {
- size: { width: 30, height: 30 },
+export const Button = Box.define('Button', {
+ /* default properties */
buttonState: false,
propagation: 0,
+
+ size: { width: 30, height: 30 },
attrs: {
- 'rect.body': { fill: 'white', stroke: 'black', 'stroke-width': 2, width: 30, height: 30 },
'.btnface': {
- stroke: 'black', 'stroke-width': 2,
- 'ref-height': .6, 'ref-width': .6, 'ref-x': .2, 'ref-y': .2,
+ stroke: 'black', strokeWidth: 2,
+ refX: .2, refY: .2,
+ refHeight: .6, refWidth: .6,
cursor: 'pointer'
}
}
}, {
- constructor: function(args) {
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: 1 }),
- '',
- '',
- '',
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function(args) {
+ Box.prototype.initialize.apply(this, arguments);
+ this.addPort({ id: 'out', group: 'out', dir: 'out', bits: 1 });
},
operation: function() {
return { out: this.get('buttonState') ? Vector3vl.ones(1) : Vector3vl.zeros(1) };
- }
+ },
+ markup: Box.prototype.markup.concat([{
+ tagName: 'rect',
+ className: 'btnface'
+ }
+ ])
});
-export const ButtonView = GateView.extend({
- presentationAttributes: GateView.addPresentationAttributes({
+export const ButtonView = BoxView.extend({
+ presentationAttributes: BoxView.addPresentationAttributes({
buttonState: 'flag:buttonState',
}),
initialize: function() {
- GateView.prototype.initialize.apply(this, arguments);
+ BoxView.prototype.initialize.apply(this, arguments);
this.$(".btnface").toggleClass('live', this.model.get('buttonState'));
},
confirmUpdate(flags) {
- GateView.prototype.confirmUpdate.apply(this, arguments);
+ BoxView.prototype.confirmUpdate.apply(this, arguments);
if (this.hasFlag(flags, 'flag:buttonState')) {
this.$(".btnface").toggleClass('live', this.model.get('buttonState'));
}
@@ -258,40 +276,46 @@ export const ButtonView = GateView.extend({
});
// Input/output model
-export const IO = Gate.define('IO', {
+export const IO = Box.define('IO', {
+ /* default properties */
bits: 1,
+ net: '',
propagation: 0,
+
attrs: {
- '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 },
- text: {
- fill: 'black',
- 'ref-x': .5, 'ref-y': .5, 'dominant-baseline': 'ideographic',
- 'text-anchor': 'middle',
- 'font-weight': 'bold',
- 'font-size': '14px'
+ 'text.ioname': {
+ refX: .5, refY: .5,
+ xAlignment: 'middle', yAlignment: 'middle',
+ fontWeight: 'bold',
+ fontSize: '10pt'
}
}
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- this.markup = [
- this.addWire(args, this.io_dir == 'out' ? 'right' : 'left', 0.5, { id: this.io_dir, dir: this.io_dir, bits: args.bits }),
- '',
- '',
- ].join('');
- if ('bits' in args) _.set(args, ['attrs', 'circle', 'port', 'bits'], args.bits);
- _.set(args, ['attrs', 'text', 'text'], args.net);
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function(args) {
+ Box.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPort({ id: this.io_dir, group: this.io_dir, dir: this.io_dir, bits: bits });
+
+ this.on('change:bits', (_, bits) => {
+ this.setPortBits(this.io_dir, bits);
+ });
+ this.bindAttrToProp('text.ioname/text', 'net');
},
- gateParams: Gate.prototype.gateParams.concat(['bits','net'])
+ markup: Box.prototype.markup.concat([{
+ tagName: 'text',
+ className: 'ioname'
+ }
+ ]),
+ gateParams: Box.prototype.gateParams.concat(['bits','net'])
});
-export const IOView = GateView.extend({
- render: function() {
- GateView.prototype.render.apply(this, arguments);
- if (this.model.get('box_resized')) return;
- this.model.set('box_resized', true);
- const width = this.el.querySelector('text.ioname').getBBox().width + 10;
- this.model.set('size', _.set(_.clone(this.model.get('size')), 'width', width));
+export const IOView = BoxView.extend({
+ autoResizeBox: true,
+ calculateBoxWidth: function() {
+ const text = this.el.querySelector('text.ioname');
+ if (text.getAttribute('display') !== 'none') return text.getBBox().width + 10;
+ return 20;
}
});
@@ -311,77 +335,89 @@ export const OutputView = IOView;
// Constant
export const Constant = NumBase.define('Constant', {
+ /* default properties */
+ constant: '0',
propagation: 0,
+
attrs: {
- '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 },
- 'text.value': {
- text: '',
- 'ref-x': .5, 'ref-y': .5,
- 'dominant-baseline': 'ideographic',
+ 'text.value': {
+ refX: .5, refY: .5,
+ yAlignment: 'middle'
}
}
}, {
- constructor: function(args) {
- args.constantCache = Vector3vl.fromBin(args.constant, args.constant.length);
- args.bits = args.constant.length;
- args.outputSignals = { out: args.constantCache };
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: args.constant.length }),
- '',
- this.numbaseMarkup,
- '',
- '',
- ].join('');
- NumBase.prototype.constructor.apply(this, arguments);
- },
initialize: function(args) {
NumBase.prototype.initialize.apply(this, arguments);
- const settext = () => {
- this.attr('text.value/text', help.sig2base(this.get('constantCache'), this.get('numbase')));
- }
+
+ const constant = this.prop('constant');
+ this.prop('bits', constant.length);
+ this.prop('constantCache', Vector3vl.fromBin(constant, constant.length));
+
+ this.addPort({ id: 'out', group: 'out', dir: 'out', bits: constant.length });
+
+ const settext = () => this.attr('text.value/text', help.sig2base(this.get('constantCache'), this.get('numbase')));
settext();
- this.listenTo(this, 'change:numbase', settext);
+
+ this.on('change:constant', (_, constant) => {
+ this.setPortBits('out', constant.length);
+ this.prop('bits', constant.length);
+ this.prop('constantCache', Vector3vl.fromBin(constant, constant.length));
+ settext();
+ });
+ this.on('change:numbase', settext);
},
operation: function() {
return { out: this.get('constantCache') };
},
+ markup: NumBase.prototype.markup.concat([{
+ tagName: 'text',
+ className: 'value numvalue'
+ }
+ ]),
gateParams: NumBase.prototype.gateParams.concat(['constant'])
});
export const ConstantView = NumBaseView;
// Clock
-export const Clock = Gate.define('Clock', {
- size: { width: 30, height: 30 },
- attrs: {
- 'rect.body': { fill: 'white', stroke: 'black', 'stroke-width': 2, width: 30, height: 30 },
- 'path.decor': { stroke: 'black' },
- '.tooltip': {
- 'ref-x': 0, 'ref-y': -30,
- width: 80, height: 30
- },
- }
+export const Clock = Box.define('Clock', {
+ /* default properties */
+ propagation: 100,
+
+ size: { width: 30, height: 30 }
}, {
- constructor: function(args) {
- args.outputSignals = { out: Vector3vl.zeros(1) };
- this.markup = [
- this.addWire(args, 'right', 0.5, { id: 'out', dir: 'out', bits: 1 }),
- '',
- '',
- '',
- '',
- '',
- '',
- ''
- ].join('');
- Gate.prototype.constructor.apply(this, arguments);
+ initialize: function(args) {
+ Box.prototype.initialize.apply(this, arguments);
+
+ this.addPort({ id: 'out', group: 'out', dir: 'out', bits: 1 });
+
+ this.prop('outputSignals/out', Vector3vl.zeros(1));
},
operation: function() {
+ // trigger next clock edge
this.trigger("change:inputSignals", this, {});
return { out: this.get('outputSignals').out.not() };
- }
+ },
+ tooltipMinWidth: 55,
+ markup: Box.prototype.markup.concat([{
+ tagName: 'path',
+ className: 'decor',
+ attributes: { d: 'M7.5 7.5 L7.5 22.5 L15 22.5 L15 7.5 L22.5 7.5 L22.5 22.5', stroke: 'black' }
+ }, {
+ tagName: 'foreignObject',
+ className: 'tooltip',
+ children: [{
+ tagName: 'body',
+ namespaceURI: 'http://www.w3.org/1999/xhtml',
+ children: [{
+ tagName: 'input',
+ attributes: { type: 'number', min: 1, step: 1 }
+ }]
+ }]
+ }
+ ])
});
-export const ClockView = GateView.extend({
- presentationAttributes: GateView.addPresentationAttributes({
+export const ClockView = BoxView.extend({
+ presentationAttributes: BoxView.addPresentationAttributes({
propagation: 'flag:propagation'
}),
events: {
@@ -391,11 +427,11 @@ export const ClockView = GateView.extend({
"input input": "changePropagation"
},
render(args) {
- GateView.prototype.render.apply(this, arguments);
+ BoxView.prototype.render.apply(this, arguments);
this.updatePropagation();
},
confirmUpdate(flags) {
- GateView.prototype.confirmUpdate.apply(this, arguments);
+ BoxView.prototype.confirmUpdate.apply(this, arguments);
if (this.hasFlag(flags, 'flag:propagation')) this.updatePropagation();
},
changePropagation(evt) {
diff --git a/src/cells/memory.mjs b/src/cells/memory.mjs
index 2d94fe6..080ddfc 100644
--- a/src/cells/memory.mjs
+++ b/src/cells/memory.mjs
@@ -10,84 +10,110 @@ import { Vector3vl, Mem3vl } from '3vl';
// Memory cell
export const Memory = Box.define('Memory', {
+ /* default properties */
+ bits: 1,
+ abits: 1,
+ rdports: [{clock_polarity: true}],
+ wrports: [{}],
+ words: undefined,
+ offset: 0,
+
attrs: {
- 'line.portsplit': {
- stroke: 'black', x1: 0, x2: 40
- },
- '.tooltip': {
- 'ref-x': 0, 'ref-y': -30,
- width: 80, height: 30
- },
+ 'path.portsplit': {
+ stroke: 'black', d: undefined
+ }
+ },
+ ports: {
+ groups: {
+ 'in': {
+ position: Box.prototype.getStackedPosition({ side: 'left' })
+ },
+ 'out': {
+ position: Box.prototype.getStackedPosition({ side: 'right' })
+ }
+ }
}
}, {
initialize: function() {
- this.listenTo(this, 'change:size', (model, size) => {
- this.attr('line.portsplit/x2', size.width);
- this.attr('.tooltip/width', size.width)
- });
Box.prototype.initialize.apply(this, arguments);
- },
- constructor: function(args) {
- if (!args.bits) args.bits = 1;
- if (!args.abits) args.abits = 1;
- if (!args.rdports) args.rdports = [];
- if (!args.wrports) args.wrports = [];
- if (!args.words) args.words = 1 << args.abits;
- if (!args.offset) args.offset = 0;
- if (args.memdata)
- this.memdata = Mem3vl.fromJSON(args.bits, args.memdata);
+
+ const bits = this.prop('bits');
+ const abits = this.prop('abits');
+ const rdports = this.prop('rdports');
+ const wrports = this.prop('wrports');
+ var words = this.prop('words');
+ const memdata = this.prop('memdata');
+
+ if (!words) {
+ words = 1 << abits;
+ this.prop('words', words, { init: true });
+ }
+ if (memdata)
+ this.memdata = Mem3vl.fromJSON(bits, memdata);
else
- this.memdata = new Mem3vl(args.bits, args.words);
- delete args.memdata; // performance hack
- console.assert(this.memdata.words == args.words);
+ this.memdata = new Mem3vl(bits, words);
+ console.assert(this.memdata.words == words);
+ this.removeProp('memdata'); // performance hack
+
this.last_clk = {};
- const markup = [];
- const lblmarkup = [];
let num = 0;
+ let idxOffset = 0;
const portsplits = [];
- function num_y(num) { return num * 16 + 12; }
- for (const [pnum, port] of args.rdports.entries()) {
+ for (const [pnum, port] of rdports.entries()) {
const portname = "rd" + pnum;
- markup.push(this.addLabelledWire(args, lblmarkup, 'right', num_y(num), { id: portname + 'data', dir: 'out', bits: args.bits, label: 'data' }));
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'addr', dir: 'in', bits: args.abits, label: 'addr' }));
- if ('enable_polarity' in port)
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'en', dir: 'in', bits: 1, label: 'en', polarity: port.enable_polarity }));
+ this.addPorts([
+ { id: portname + 'addr', group: 'in', dir: 'in', bits: bits, portlabel: 'addr' },
+ { id: portname + 'data', group: 'out', dir: 'out', bits: bits, portlabel: 'data', args: { idxOffset: idxOffset } }
+ ], { labelled: true });
+ num += 1;
+ if ('enable_polarity' in port) {
+ num++;
+ idxOffset++;
+ this.addPort({ id: portname + 'en', group: 'in', dir: 'in', bits: 1, portlabel: 'en', polarity: port.enable_polarity }, { labelled: true });
+ }
if ('clock_polarity' in port) {
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'clk', dir: 'in', bits: 1, label: 'clk', polarity: port.clock_polarity, clock: true }));
+ num++;
+ idxOffset++;
+ this.addPort({ id: portname + 'clk', group: 'in', dir: 'in', bits: 1, portlabel: 'clk', polarity: port.clock_polarity, decor: Box.prototype.decorClock }, { labelled: true });
this.last_clk[portname + 'clk'] = 0;
} else {
port.transparent = true;
}
portsplits.push(num);
}
- for (const [pnum, port] of args.wrports.entries()) {
+ for (const [pnum, port] of wrports.entries()) {
const portname = "wr" + pnum;
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'data', dir: 'in', bits: args.bits, label: 'data' }));
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'addr', dir: 'in', bits: args.abits, label: 'addr' }));
- if ('enable_polarity' in port)
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'en', dir: 'in', bits: args.bits, label: 'en', polarity: port.enable_polarity }));
+ num += 2;
+ this.addPorts([
+ { id: portname + 'data', group: 'in', dir: 'in', bits: bits, portlabel: 'data' },
+ { id: portname + 'addr', group: 'in', dir: 'in', bits: bits, portlabel: 'addr' }
+ ], { labelled: true });
+ if ('enable_polarity' in port) {
+ num++;
+ this.addPort({ id: portname + 'en', group: 'in', dir: 'in', bits: 1, portlabel: 'en', polarity: port.enable_polarity }, { labelled: true });
+ }
if ('clock_polarity' in port) {
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num_y(num++), { id: portname + 'clk', dir: 'in', bits: 1, label: 'clk', polarity: port.clock_polarity, clock: true }));
+ num++;
+ this.addPort({ id: portname + 'clk', group: 'in', dir: 'in', bits: 1, portlabel: 'clk', polarity: port.clock_polarity, decor: Box.prototype.decorClock }, { labelled: true });
this.last_clk[portname + 'clk'] = 0;
}
portsplits.push(num);
}
- const size = { width: 80, height: num*16+8 };
- args.size = size;
portsplits.pop();
- markup.push('');
- for (const num of portsplits) {
- const yline = num_y(num) - 8;
- markup.push('');
- }
- markup.push('');
- markup.push(lblmarkup.join(''));
- markup.push(['',
- '',
- '🔍',
- ''].join(''));
- this.markup = markup.join('');
- Box.prototype.constructor.apply(this, arguments);
+
+ this.on('change:size', (_, size) => {
+ // only adapting to changed width
+ const path = [];
+ for (const num of portsplits) {
+ path.push([
+ [0, 16*num + 4],
+ [size.width, 16*num + 4]
+ ].map(p => p.join(' ')).join(' L '));
+ }
+ this.attr('path.portsplit/d', 'M ' + path.join(' M '));
+ });
+ const height = num*16+8;
+ this.prop('size/height', height);
},
operation: function(data) {
const out = {};
@@ -165,15 +191,21 @@ export const Memory = Box.define('Memory', {
}
if (changed) this.set('outputSignals', sigs);
},
+ markup: Box.prototype.markup.concat([{
+ tagName: 'path',
+ className: 'portsplit'
+ }], Box.prototype.markupZoom),
getGateParams: function() {
// hack to get memdata back
const params = Box.prototype.getGateParams.apply(this, arguments);
params.memdata = this.memdata.toJSON();
return params;
},
- gateParams: Box.prototype.gateParams.concat(['bits', 'abits', 'rdports', 'wrports', 'words', 'offset'])
+ gateParams: Box.prototype.gateParams.concat(['bits', 'abits', 'rdports', 'wrports', 'words', 'offset']),
+ unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['bits', 'abits', 'rdports', 'wrports', 'words', 'offset'])
});
export const MemoryView = BoxView.extend({
+ autoResizeBox: true,
events: {
"click foreignObject.tooltip": "stopprop",
"mousedown foreignObject.tooltip": "stopprop",
@@ -224,7 +256,7 @@ export const MemoryView = BoxView.extend({
col.text('0'.repeat(ahex - addrs.length) + addrs)
col = col.next();
for (let c = 0; c < columns; c++, col = col.next()) {
- if (address + r * columns + c > words) break;
+ if (address + r * columns + c >= words) break;
col.find('input').val(help.sig2base(memdata.get(address + r * columns + c), numbase))
.removeClass('invalid');
}
diff --git a/src/cells/mux.mjs b/src/cells/mux.mjs
index ac826f5..0be008a 100644
--- a/src/cells/mux.mjs
+++ b/src/cells/mux.mjs
@@ -1,56 +1,68 @@
"use strict";
import * as joint from 'jointjs';
-import { Gate, GateView } from './base';
+import { Gate, GateView, portGroupAttrs } from './base';
import bigInt from 'big-integer';
import * as help from '../help.mjs';
import { Vector3vl } from '3vl';
// Multiplexers
export const GenMux = Gate.define('GenMux', {
- attrs: {
- "rect.wtf": {
- y: -4, width: 40, height: 1, visibility: 'hidden'
- },
- "text.arrow": {
- text: '✔', 'y-alignment': 'middle', fill: 'black',
- visibility: 'hidden'
+ /* default properties */
+ bits: { in: 1, sel: 1 },
+
+ size: { width: 40, height: undefined },
+ ports: {
+ groups: {
+ 'in2': {
+ position: { name: 'top', args: { y: 10 } },
+ attrs: _.merge({}, portGroupAttrs, { 'line.wire': { x2: 0, y2: -30 }, 'circle.port': { magnet: 'passive', refY: -30 }, 'text.bits': { refDx: -4, refDy: 3, textAnchor: 'start' } }),
+ z: -1
+ }
}
}
}, {
- constructor: function(args) {
- if (!args.bits) args.bits = { in: 1, sel: 1 };
- const n_ins = this.muxNumInputs(args.bits.sel);
- const size = { width: 40, height: n_ins*16+8 };
- _.set(args, ['attrs', '.body', 'points'],
- [[0,0],[40,10],[40,size.height-10],[0,size.height]]
+ initialize: function() {
+ Gate.prototype.initialize.apply(this, arguments);
+
+ const bits = this.prop('bits');
+
+ this.addPorts([
+ { id: 'sel', group: 'in2', dir: 'in', bits: bits.sel },
+ { id: 'out', group: 'out', dir: 'out', bits: bits.in }
+ ]);
+
+ this.on('change:size', (_, size) => {
+ this.attr(['polygon.body', 'points'],
+ [[0,0],[size.width,10],[size.width,size.height-10],[0,size.height]]
.map(x => x.join(',')).join(' '));
- args.size = size;
- const markup = [];
+ });
+ const n_ins = this.muxNumInputs(bits.sel);
+ this.prop('size/height', n_ins*16+8);
+
+ const vpath = [
+ [2, 0],
+ [5, 5],
+ [11, -5]
+ ];
+ const path = 'M' + vpath.map(l => l.join(' ')).join(' L');
+
for (const num of Array(n_ins).keys()) {
- const y = num*16+12;
- markup.push(this.addWire(args, 'left', y, { id: 'in' + num, dir: 'in', bits: args.bits.in }));
+ this.addPort({ id: 'in' + num, group: 'in', dir: 'in', bits: bits.in, decor: path });
}
- markup.push(this.addWire(args, 'top', 0.5, { id: 'sel', dir: 'in', bits: args.bits.sel }));
- markup.push(this.addWire(args, 'right', (size.height)/2, { id: 'out', dir: 'out', bits: args.bits.in }));
- markup.push('');
- for (const num of Array(n_ins).keys()) {
- const y = num*16+12;
- markup.push('');
- args.attrs['text.arrow_in' + num] = {
- 'ref-x': 2,
- 'ref-y': y,
- };
- }
- this.markup = markup.join('');
- Gate.prototype.constructor.apply(this, arguments);
},
operation: function(data) {
const i = this.muxInput(data.sel);
if (i === undefined) return { out: Vector3vl.xes(this.get('bits').in) };
return { out: data['in' + i] };
},
- gateParams: Gate.prototype.gateParams.concat(['bits'])
+ markup: Gate.prototype.markup.concat([{
+ tagName: 'polygon',
+ className: 'body'
+ }
+ ]),
+ gateParams: Gate.prototype.gateParams.concat(['bits']),
+ unsupportedPropChanges: Gate.prototype.unsupportedPropChanges.concat(['bits'])
});
export const GenMuxView = GateView.extend({
initialize() {
@@ -70,7 +82,7 @@ export const GenMuxView = GateView.extend({
updateMux(data) {
const i = this.model.muxInput(data.sel);
for (const num of Array(this.n_ins).keys()) {
- this.$('text.arrow_in' + num).css('visibility', i == num ? 'visible' : 'hidden');
+ this.$('[port=in' + num + '] path.decor').css('visibility', i == num ? 'visible' : 'hidden');
}
}
});
diff --git a/src/cells/subcircuit.mjs b/src/cells/subcircuit.mjs
index 2218e87..a44e8cf 100644
--- a/src/cells/subcircuit.mjs
+++ b/src/cells/subcircuit.mjs
@@ -1,35 +1,30 @@
"use strict";
import * as joint from 'jointjs';
-import { Gate, Box, BoxView } from './base';
+import { Box, BoxView } from './base';
import { IO, Input, Output } from './io';
import bigInt from 'big-integer';
import * as help from '../help.mjs';
// Subcircuit model -- embeds a circuit graph in an element
export const Subcircuit = Box.define('Subcircuit', {
+ /* default properties */
propagation: 0,
+
attrs: {
- 'path.wire' : { 'ref-y': .5, stroke: 'black' },
'text.type': {
- text: '', 'ref-x': 0.5, 'ref-y': -10,
- 'dominant-baseline': 'ideographic',
- 'text-anchor': 'middle',
- fill: 'black'
- },
- '.tooltip': {
- 'ref-x': 0, 'ref-y': -30,
- width: 80, height: 30
- },
+ refX: .5, refY: -10,
+ xAlignment: 'middle', yAlignment: 'middle'
+ }
}
}, {
initialize: function() {
- this.listenTo(this, 'change:size', (model, size) => this.attr('.tooltip/width', size.width));
Box.prototype.initialize.apply(this, arguments);
- },
- constructor: function(args) {
- console.assert(args.graph instanceof joint.dia.Graph);
- const graph = args.graph;
+
+ this.bindAttrToProp('text.type/text', 'celltype');
+
+ const graph = this.prop('graph');
+ console.assert(graph instanceof joint.dia.Graph);
graph.set('subcircuit', this);
const IOs = graph.getCells()
.filter((cell) => cell instanceof IO);
@@ -44,39 +39,32 @@ export const Subcircuit = Box.define('Subcircuit', {
outputs.sort(sortfun);
const vcount = Math.max(inputs.length, outputs.length);
const size = { width: 80, height: vcount*16+8 };
- const markup = [];
- const lblmarkup = [];
const iomap = {};
- _.set(args, ['attrs', 'text.type', 'text'], args.celltype);
- args.inputSignals = args.inputSignals || {};
- args.outputSignals = args.outputSignals || {};
for (const [num, io] of inputs.entries()) {
- markup.push(this.addLabelledWire(args, lblmarkup, 'left', num*16+12, { id: io.get('net'), dir: 'in', bits: io.get('bits') }));
- args.inputSignals[io.get('net')] = io.get('outputSignals').out;
+ this.addPort({ id: io.get('net'), group: 'in', dir: 'in', bits: io.get('bits') }, { labelled: true });
+ this.prop(['inputSignals', io.get('net')], io.get('outputSignals').out);
}
for (const [num, io] of outputs.entries()) {
- markup.push(this.addLabelledWire(args, lblmarkup, 'right', num*16+12, { id: io.get('net'), dir: 'out', bits: io.get('bits') }));
- args.outputSignals[io.get('net')] = io.get('inputSignals').in;
+ this.addPort({ id: io.get('net'), group: 'out', dir: 'out', bits: io.get('bits') }, { labelled: true });
+ this.prop(['outputSignals', io.get('net')], io.get('inputSignals').in);
}
- markup.push('');
- markup.push(lblmarkup.join(''));
for (const io of IOs) {
iomap[io.get('net')] = io.get('id');
}
- markup.push('');
- markup.push('');
- markup.push('🔍')
- markup.push('');
- this.markup = markup.join('');
- args.size = size;
- args.attrs['rect.body'] = size;
- args.circuitIOmap = iomap;
- Gate.prototype.constructor.apply(this, arguments);
+ this.prop('size', size);
+ this.prop('circuitIOmap', iomap);
},
- gateParams: Box.prototype.gateParams.concat(['celltype'])
+ markup: Box.prototype.markup.concat([{
+ tagName: 'text',
+ className: 'type'
+ }
+ ], Box.prototype.markupZoom),
+ gateParams: Box.prototype.gateParams.concat(['celltype']),
+ unsupportedPropChanges: Box.prototype.unsupportedPropChanges.concat(['celltype'])
});
export const SubcircuitView = BoxView.extend({
+ autoResizeBox: true,
events: {
"click foreignObject.tooltip": "stopprop",
"mousedown foreignObject.tooltip": "stopprop",
diff --git a/src/circuit.mjs b/src/circuit.mjs
index 6c90bb8..38018c8 100644
--- a/src/circuit.mjs
+++ b/src/circuit.mjs
@@ -122,7 +122,8 @@ export class HeadlessCircuit {
}
}
function clearInput(end, gate) {
- setInput(Vector3vl.xes(gate.ports[end.port].bits), end, gate);
+ var bits = gate.getPort(end.port).bits;
+ setInput(Vector3vl.xes(bits), end, gate);
}
this.listenTo(graph, 'change:target', function(wire, end) {
const gate = graph.getCell(end.id);
@@ -149,7 +150,7 @@ export class HeadlessCircuit {
const sgate = graph.getCell(strt.id);
if (sgate && 'port' in strt) {
cell.set('signal', sgate.get('outputSignals')[strt.port]);
- cell.set('bits', sgate.ports[strt.port].bits);
+ cell.set('bits', sgate.getPort(strt.port).bits);
}
});
let laid_out = false;
@@ -167,8 +168,8 @@ export class HeadlessCircuit {
}
for (const conn of data.connectors) {
graph.addCell(new this._cells.Wire({
- source: {id: conn.from.id, port: conn.from.port},
- target: {id: conn.to.id, port: conn.to.port},
+ source: {id: conn.from.id, port: conn.from.port, magnet: '.port'},
+ target: {id: conn.to.id, port: conn.to.port, magnet: '.port'},
netname: conn.name,
vertices: conn.vertices || []
}));
diff --git a/src/index.mjs b/src/index.mjs
index 59899a2..faeb7cb 100644
--- a/src/index.mjs
+++ b/src/index.mjs
@@ -90,24 +90,27 @@ export class Circuit extends HeadlessCircuit {
el: elem,
model: graph,
width: 100, height: 100, gridSize: 5,
+ magnetThreshold: 'onleave',
snapLinks: true,
linkPinning: false,
+ markAvailable: true,
defaultLink: new this._cells.Wire,
cellViewNamespace: this._cells,
validateConnection: function(vs, ms, vt, mt, e, vl) {
if (e === 'target') {
if (!mt) return false;
- const pt = vt.model.ports[mt.getAttribute('port')];
+ const pt = vt.model.getPort(vt.findAttribute('port', mt));
if (typeof pt !== 'object' || pt.dir !== 'in' || pt.bits !== vl.model.get('bits'))
return false;
const link = this.model.getConnectedLinks(vt.model).find((l) =>
l.id !== vl.model.id &&
l.get('target').id === vt.model.id &&
- l.get('target').port === mt.getAttribute('port')
+ l.get('target').port === vt.findAttribute('port', mt)
);
return !link;
} else if (e === 'source') {
- const ps = vs.model.ports[ms.getAttribute('port')];
+ if (!ms) return false;
+ const ps = vs.model.getPort(vs.findAttribute('port', ms));
if (typeof ps !== 'object' || ps.dir !== 'out' || ps.bits !== vl.model.get('bits'))
return false;
return true;
diff --git a/src/style.css b/src/style.css
index 7c580c1..a141872 100644
--- a/src/style.css
+++ b/src/style.css
@@ -100,45 +100,26 @@
cursor: crosshair;
}
-.joint-element .body {
- fill: white;
- stroke: black;
- transition: all 0.2s;
-}
-
-.djs .joint-element circle {
- fill: #fff;
- stroke: #7f8c8d;
- stroke-opacity: 0.5;
- stroke-width: 2px;
-}
-
-.djs .joint-element circle.live {
+.djs .joint-port-body.defined.live circle.port {
stroke: #03c03c;
}
-.djs .joint-element circle.low {
+.djs .joint-port-body.defined.low circle.port {
stroke: #fc7c68;
}
-.djs .joint-element circle.defined {
+.djs .joint-port-body.defined circle.port {
stroke: #779ecb;
}
.djs .joint-element circle.led.live {
fill: #03c03c;
+ stroke-width: 0;
}
.djs .joint-element circle.led.low {
fill: #fc7c68;
-}
-
-.joint-element text {
- font-size: 8pt;
-}
-
-.joint-element text.bits {
- font-size: 7pt;
+ stroke-width: 0;
}
.joint-link.live > .connection {
@@ -631,10 +612,6 @@ g:hover > foreignObject.tooltip {
padding: 5px;
}
-.joint-element foreignObject.tooltip a.zoom {
- cursor: pointer;
-}
-
div.wire_hover {
position: fixed;
pointer-events: none;