Module: HDLRuby::Viz

Defined in:
lib/HDLRuby/hruby_viz.rb

Defined Under Namespace

Classes: IC, Node, Port

Constant Summary collapse

LEFT =

Directions/sides encoding: left=1, up=2, right=4, down=8, blocked=16

1
UP =
2
RIGHT =
4
DOWN =
8
BLOCKED =
16

Class Method Summary collapse

Class Method Details

.closing_svg(name, side, xpos, ypos, visibility = "hidden") ⇒ Object

Generate a closing button symbol. (Invisible by default, if "" is given as visibilty, not set here.



4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
# File 'lib/HDLRuby/hruby_viz.rb', line 4393

def self.closing_svg(name,side,xpos,ypos,visibility="hidden")
  # The rectangle of the button.
  if visibility.empty? then
    res  = "<g id=\"#{name}\" >\n"
  else
    res  = "<g id=\"#{name}\" visibility=\"#{visibility}\">\n"
  end
  res += "<rect fill=\"#cd5c5c\" "+
    "stroke=\"#000\" stroke-width=\"#{(side/8.0).round(3)}\" " +
    "x=\"#{xpos.round(3)}\" y=\"#{ypos.round(3)}\" " +
    "width=\"#{side.round(3)}\" height=\"#{side.round(3)}\" />\n"
  # The cross lines.
  res += "<line stroke=\"#000\" stroke-width=\"#{(side/8.0).round(3)}\" " +
    "stroke-linecap=\"butt\" " +
    "x1=\"#{xpos.round(3)}\" y1=\"#{ypos.round(3)}\" " +
    "x2=\"#{(xpos+side).round(3)}\" y2=\"#{(ypos+side).round(3)}\" " +
    " />\n"
  res += "<line stroke=\"#000\" stroke-width=\"#{(side/8.0).round(3)}\" " +
    "stroke-linecap=\"butt\" " +
    "x1=\"#{(xpos+side).round(3)}\" y1=\"#{ypos.round(3)}\" " +
    "x2=\"#{xpos.round(3)}\" y2=\"#{(ypos+side).round(3)}\" " +
    " />\n"
  res += "</g>"
end

.header_svg(name, x0, y0, width, height) ⇒ Object

Generate the svg header, global control scripts and helping panel.



4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
# File 'lib/HDLRuby/hruby_viz.rb', line 4300

def self.header_svg(name,x0,y0,width,height)
  return <<~SVG
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="svg:#{name}" width="100%" height="100%" 
   viewBox="#{x0} #{y0} #{width} #{height}" 
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink" >

<script>
const diagram = document.getElementById('svg:#{name}');
const zoom = 1.0;
const deltaR = 100.0;
let x0 = #{x0};
let y0 = #{y0};
let width = #{width};
let height = #{height};
diagram.setAttribute('viewBox', `${x0} ${y0} ${width} ${height}`);

// Create an SVGPoint for conputing the location in the diagram
let pt = diagram.createSVGPoint();

// Get the location of the cursor in the diagram.
function cursorPoint(e){
pt.x = e.clientX; pt.y = e.clientY;
return pt.matrixTransform(diagram.getScreenCTM().inverse());
}

// Zooming function.
diagram.addEventListener("wheel", (e) => {
  let factor = 1.0 / (zoom * Math.exp(e.deltaY/deltaR))
  let loc = cursorPoint(e);
  x0 = x0 * factor;
  y0 = y0 * factor;
  width = width * factor;
  height = height * factor;
  let dx = (-loc.x) * (factor-1);
  let dy = (-loc.y) * (factor-1);
  x0 += dx;
  y0 += dy;
  diagram.setAttribute('viewBox', `${x0} ${y0} ${width} ${height}`);
});

// Moving function.
let isMoving = false;
let prevLoc;

diagram.addEventListener("mousedown", (e) => {
  prevLoc = cursorPoint(e);
  isMoving = true;
});

diagram.addEventListener("mouseup", (e) => {
  isMoving = false;
});

diagram.addEventListener("mousemove", (e) => {
  if (isMoving) {
      let loc = cursorPoint(e);
      let dx = prevLoc.x-loc.x;
      let dy = prevLoc.y-loc.y;
      x0 += dx;
      y0 += dy;
      diagram.setAttribute('viewBox', `${x0} ${y0} ${width} ${height}`);
  }
});

diagram.addEventListener("keydown", (e) => {
  switch(e.key) {
      case 'ArrowLeft':
          x0 -= width/100.0;
          break;
      case 'ArrowUp':
          y0 -= width/100.0;
          break;
      case 'ArrowRight':
          x0 += width/100.0;
          break;
      case 'ArrowDown':
          y0 += width/100.0;
          break;
  }
  diagram.setAttribute('viewBox', `${x0} ${y0} ${width} ${height}`);
});

</script>

SVG
end

.help_svg(x0, y0, width, height) ⇒ Object

Generate the help panel and its handling.



4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
# File 'lib/HDLRuby/hruby_viz.rb', line 4419

def self.help_svg(x0,y0,width,height)
  # Compute the fit factor.
  fit = 1024.0/[width,height].min
  return <<~SVG
// Helping button.
<g id="$help$_open">
<rect fill="yellow" stroke="#000" stroke-width="3"
 x="#{(x0+width-62/fit).round(3)}" y="#{(y0+2/fit).round(3)}" width="#{(60/fit).round(3)}" height="#{(30/fit).round(3)}" />
<text style="text-anchor: middle; dominant-baseline: middle;" 
      font-family="monospace" font-size="#{(20/fit).round(3)}px"
      x="#{(x0+width-32/fit).round(3)}" y="#{(y0+17/fit).round(3)}" >
  Help
</text>
</g>

// Helping panel.
<g id="$help$" visibility="hidden">
<rect fill="#ffffd7" stroke="#000" stroke-width="6"
 x="#{x0.round(3)}" y="#{y0.round(3)}" width="#{width.round(3)}" height="#{height.round(3)}" />
 <text font-size="#{(40/fit).round(3)}px" font-family="serif" font-weight="bold"
  x="#{(x0+width/2).round(3)}" y="#{(y0+40/fit).round(3)}" >
  Help
 </text>

 <text font-size="#{(30/fit).round(3)}px" font-family="serif" font-weight="bold"
  x="#{(x0+15/fit).round(3)}" y="#{(y0+100/fit).round(3)}" >
 Navigation:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+25/fit).round(3)}" y="#{(y0+140/fit).round(3)}" >
 Zoom in/out:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+190/fit).round(3)}" y="#{(y0+140/fit).round(3)}" >
 mouse wheel.
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+25/fit).round(3)}" y="#{(y0+170/fit).round(3)}" >
 Move diagram:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+190/fit).round(3)}" y="#{(y0+170/fit).round(3)}" >
 left click and drag, or arrow keys.
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+25/fit).round(3)}" y="#{(y0+200/fit).round(3)}" >
 Open element:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+190/fit).round(3)}" y="#{(y0+200/fit).round(3)}" >
 left click on element.
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+25/fit).round(3)}" y="#{(y0+230/fit).round(3)}" >
 Close element:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+190/fit).round(3)}" y="#{(y0+230/fit).round(3)}" >
 left click on the close button at the top right of the element.
 </text>

 <text font-size="#{(30/fit).round(3)}px" font-family="serif" font-weight="bold"
  x="#{(x0+15/fit).round(3)}" y="#{(y0+300/fit).round(3)}" >
 Types of elements:
 </text>
 #{
 ic = IC.new("Instance",:instance)
 ic.scale = 35.0/fit
 ic.xpos = (x0+15)/fit / ic.scale
 ic.ypos = 330/fit / ic.scale
 ic.width = 300/fit / ic.scale
 ic.height = 100/fit / ic.scale
 ic.instance_svg(ic)
 }
 #{
 ic = IC.new("Continuous assignment",:alu)
 ic.scale = 35.0/fit
 ic.xpos = (x0+(15+300+15))/fit / ic.scale
 ic.ypos = 305/fit / ic.scale
 ic.width = 300/fit / ic.scale
 ic.height = 150/fit / ic.scale
 p0 = Port.new("in0",ic,:input)
 p0.side = LEFT
 ic.ports << p0
 p1 = Port.new("in1",ic,:input)
 p1.side = LEFT
 ic.ports << p1
 p2 = Port.new("out",ic,:output)
 p2.side = RIGHT
 ic.alu_svg(ic)
 ic.ports << p2
 ic.alu_svg(ic)
 }
 #{
 ic = IC.new("Combinatorial process",:process)
 ic.scale = 35.0/fit
 ic.xpos = (x0+15)/fit / ic.scale
 ic.ypos = 480/fit / ic.scale
 ic.width = 300/fit / ic.scale
 ic.height = 100/fit / ic.scale
 ic.process_svg(ic)
 }
 #{
 ic = IC.new("Clocked process",:clocked_process)
 ic.scale = 35.0/fit
 ic.xpos = (x0+(15+300+15))/fit / ic.scale
 ic.ypos = 480/fit / ic.scale
 ic.width = 300/fit / ic.scale
 ic.height = 100/fit / ic.scale
 ic.clocked_process_svg(ic)
 }
 #{
 ic = IC.new("Time process",:timed_process)
 ic.scale = 35.0/fit
 ic.xpos = (x0+(15+300+15+300+15))/fit / ic.scale
 ic.ypos = 480/fit / ic.scale
 ic.width = 300/fit / ic.scale
 ic.height = 100/fit / ic.scale
 ic.timed_process_svg(ic)
 }
 #{
 ic = IC.new("Signal",:register)
 ic.scale = 35.0/fit
 ic.xpos = (x0+15)/fit / ic.scale
 ic.ypos = 600/fit / ic.scale
 ic.width = 150/fit / ic.scale
 ic.height = 75/fit / ic.scale
 ic.register_svg(ic)
 }
 #{
 ic = IC.new("Memory",:memory)
 ic.scale = 35.0/fit
 ic.xpos = (x0+(15+200+15))/fit / ic.scale
 ic.ypos = 600/fit / ic.scale
 ic.width = 200/fit / ic.scale
 ic.height = 100/fit / ic.scale
 ic.memory_svg(ic)
 }

 <text font-size="#{(30/fit).round(3)}px" font-family="serif" font-weight="bold"
  x="#{(x0+15/fit).round(3)}" y="#{(y0+800/fit).round(3)}" >
 Contents of processes and assigments:
 </text>
 <text font-size="#{(25/fit).round(3)}px" font-family="serif" x="#{(x0+25/fit).round(3)}" y="#{(y0+840/fit).round(3)}" >
 Represented as sets of parallel flow charts.
 </text>

 // Closing button for the helping panel.
 #{Viz.closing_svg("$help$_close",20/fit,width-20/fit,0,"")}
</g>

// Control of the helping panel.
<script>
diagram.getElementById('$help$_open').addEventListener("click", (e) => {
  // For the element.
  let elem = diagram.getElementById('$help$');
  elem.setAttribute('visibility','visible');
});
diagram.getElementById('$help$_close').addEventListener("click", (e) => {
  // For the element.
  let elem = diagram.getElementById('$help$');
  elem.setAttribute('visibility','hidden');
});
</script>

SVG
end

.port_base_name(name) ⇒ Object

Get the base name of the port.



16
17
18
# File 'lib/HDLRuby/hruby_viz.rb', line 16

def self.port_base_name(name)
  return name.sub(/\[.*\]\z/,"")
end

.port_bit_name(name) ⇒ Object

Get the bit part of the port name if any.



21
22
23
# File 'lib/HDLRuby/hruby_viz.rb', line 21

def self.port_bit_name(name)
  return name.match(/\[.*\]\z/)[0]
end

.port_bit_name?(name) ⇒ Boolean

Tell if a port name is for a sub port.

Returns:

  • (Boolean)


26
27
28
# File 'lib/HDLRuby/hruby_viz.rb', line 26

def self.port_bit_name?(name)
  return name =~ /\[.*\]\z/
end

.reg2input_name(name) ⇒ Object

Generate an input register name from name



4289
4290
4291
# File 'lib/HDLRuby/hruby_viz.rb', line 4289

def self.reg2input_name(name)
  return name + "$I"
end

.reg2output_name(name) ⇒ Object

Generate an output register name from name



4294
4295
4296
# File 'lib/HDLRuby/hruby_viz.rb', line 4294

def self.reg2output_name(name)
  return name + "$O"
end

.svg_text_fit(name, width, max_size) ⇒ Object

Generate a script for resizing a font to fit width for text object name, with max font size max_size px.



4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
# File 'lib/HDLRuby/hruby_viz.rb', line 4581

def self.svg_text_fit(name,width,max_size)
  return <<~SCRIPT
<script> 
  fitWidth=#{width.round(3)};
  textNode = document.getElementById("#{self.to_svg_id(name)}");
  textBB = textNode.getBBox();
  fitSize = fitWidth / textBB.width;
  if (fitSize > #{max_size.round(3)}) fitSize = #{max_size.round(3)};
  textNode.setAttribute("font-size", fitSize + "px")
 </script>
SCRIPT
end

.to_svg_id(name) ⇒ Object

Converts to a SVG-compatible id.



4284
4285
4286
# File 'lib/HDLRuby/hruby_viz.rb', line 4284

def self.to_svg_id(name)
  return name.gsub(/ /,"")
end

.to_svg_text(name) ⇒ Object

Converts to a SVG-compatible text.



4279
4280
4281
# File 'lib/HDLRuby/hruby_viz.rb', line 4279

def self.to_svg_text(name)
  return name.gsub(/&/,"&amp;").gsub(/</,"&lt;").gsub(/-@/,"-").gsub(/\+@/,"+")
end