Class: HDLRuby::High::Std::Board

Inherits:
Object
  • Object
show all
Includes:
Hmissing
Defined in:
lib/HDLRuby/ui/hruby_board.rb

Overview

Class describing a board.

Defined Under Namespace

Classes: ASCII, BITMAP, BT, DIGIT, HEXA, HOOK, LED, PROW, SCOPE, SLIDER, SW, TEXT

Constant Summary collapse

TO_COMPUTE =

Suffix telling a text is an expression to compute.

"="
UI_header =

The base HTML/Javascript/Ajax code of the FPGA UI: header

<<-HTMLHEADER
HTTP/1.1 200
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
<style>

  html {
    background-color: #2a6424;
  }

  #header {
    width:  100%;
    /* border: solid 2px white; */
    text-align: center;
  }

  .panel {
    width:  100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-content: flex-start;
    gap: 20px;
  }

  .prow {
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: center;
    gap: 15px;
  }

  .title {
    font-size: 36px;
    color: #d0d0d0;
    /* color: #2a6424; */
    /* color: white; */
    /* text-shadow: -1px -1px 1px white, 1px 1px 1px #101010; */
    text-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
    background-color: #265a20;
    box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    padding: 4px;
    padding-left: 16px;
    padding-right: 16px;
    display: inline-block;
  }

  .name {
    font-size: 20px;
    font-family: "Lucida Console", "Courier New", monospace;
    color: white;
    background-color: #265a20;
    box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    padding-left:  8px;
    padding-right: 8px;
    /* display: table-cell; 
    vertical-align: middle; */
    height: 28px;
    line-height: 28px;
  }

  .vl {
    /* border-left: 10px solid #2a6424; */
    color: #2a6424;
    box-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
    -moz-box-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
    -webkit-shadow: -1px -1px 1px #63c359, 1px 1px 1px #153212;
    width: 10px;
  }

  .hdiv {
    display: flex;
    flex-direction: row;
    align-item: stretch;
  }

  .vdiv {
    display: flex;
    flex-direction: column;
    justify-content: stretch;
  }

  .swset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .btset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .sliderset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .ledset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .digitset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .hexaset {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-left:  8px;
    margin-right: 8px;
    height: 40px;
  }

  .scopetitle {
    font-size: 20px;
    font-family: "Lucida Console", "Courier New", monospace;
    color: white;
    background-color: #265a20;
    box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -moz-box-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    -webkit-shadow: -1px -1px 1px #153212, 1px 1px 1px #63c359;
    display: inline-block;
    width:  30vw;
    height: 28px;
    line-height: 28px;
    margin-right: auto;
    margin-bottom: 4px;
    text-align:center;
  }

  .scopepane {
    background-color: #ccc;
    border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    padding: 10px;
    width: max-content;
    margin-left: auto;
    margin-right: auto;
  }

  .y-range {
    margin-left:  auto;
    /* border: solid 2px white; */
    display: flex;
    flex-direction: column;
  }

  .x-range {
    width:  30vw;
    /* border: solid 2px white; */
    display: flex;
    flex-direction: row;
    margin-right: auto;
    margin-left: 4px;
    margin-top: 4px;
  }

  .u-value {
    font-size: 16px;
    font-family: "Lucida Console", "Courier New", monospace;
    font-weight: bold;
    color: black;
    background-color: #ddd;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    padding-left:  8px;
    padding-right: 8px;
    text-align:center;
  }

  .d-value {
    font-size: 16px;
    font-family: "Lucida Console", "Courier New", monospace;
    font-weight: bold;
    color: black;
    background-color: #ddd;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    padding-left:  8px;
    padding-right: 8px;
    margin-top: auto;
    text-align:center;
  }

  .l-value {
    font-size: 16px;
    font-family: "Lucida Console", "Courier New", monospace;
    font-weight: bold;
    color: black;
    background-color: #ddd;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    padding-left:  8px;
    padding-right: 8px;
    height: fit-content;
  }

  .r-value {
    font-size: 16px;
    font-family: "Lucida Console", "Courier New", monospace;
    font-weight: bold;
    color: black;
    background-color: #ddd;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    padding-left:  8px;
    padding-right: 8px;
    margin-left: auto;
    height: fit-content;
  }

  .r-blank {
    font-size: 20px;
    color: rgba(0,0,0,0);
    border: solid 1px rgba(0,0,0,0);
    padding-left:  8px;
    padding-right: 8px;
    margin-left: auto;
    height: fit-content;
  }

  .scope {
    width:  30vw;
    height: 30vw;
    margin-right: auto; 
    border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }


  .screen {
    object-fit:contain;
    color: yellow;
    background-color: black;
    /* border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010; */
  }

  .sw {
    position: relative;
    display: inline-block;
    width: 24px;
    height: 40px;
    margin: 2px;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }
  
  .sw input { 
    opacity: 0;
    width: 0;
    height: 0;
  }
  
  .sw_slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #ccc;
    -webkit-transition: .2s;
    transition: .2s;
    border: solid 2px #505050;  
  }
  
  .sw_slider:before {
    position: absolute;
    content: "";
    height: 16px;
    width: 16px;
    left: 2px;
    bottom: 2px;
    background-color: black;
    -webkit-transition: .2s;
    transition: .2s;
  }
  
  input:checked + .sw_slider {
    background-color: yellow;
  }
  
  input:checked + .sw_slider:before {
    -webkit-transform: translateY(-16px);
    -ms-transform: translateY(-16px);
    transform: translateY(-16px);
  }

  .slider {
    -webkit-appearance: none;
    width: 100%;
    height: 25px;
    background-color: #ccc;
    -webkit-transition: .2s;
    transition: .2s;
    border: solid 2px #505050;  
    margin: 2px;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 25px;
    height: 25px;
    background: black;
    border: solid 2px #505050;  
    cursor: pointer;
  }

  .slider::-moz-range-thumb {
    width: 25px;
    height: 25px;
    background: black;
    border: solid 2px #505050;  
    cursor: pointer;
  }

  .matrix {
    font-size: 26px;
    font-family: "Lucida Console", "Courier New", monospace;
    color: yellow;
    background-color: black;
    border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .matrix_in {
    font-size: 26px;
    font-family: "Lucida Console", "Courier New", monospace;
    color: #0B190B;
    background-color: #9BBC0F;
    border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .bt {
    background-color: #ccc;
    border: solid 2px #505050;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .bt:hover {
    background-color: #aaa;
  }

  .bt_off { 
    height: 20px;  
    width: 20px; 
    border: solid 1px #505050;
    border-radius: 50%;
    background: linear-gradient(to bottom right, #A0A0A0, black 60%);
    display: inline-block;
    margin-top: 1px;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .bt_on {
    height: 20px;
    width: 20px;
    border: solid 1px #505050;
    border-radius: 50%;
    background: linear-gradient(to top left, #A0A0A0, black 80%);
    display: inline-block;
    margin-top: 3px;
    margin-bottom: -2px;
    box-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
    -moz-box-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
    -webkit-shadow: -1px -1px 1px #101010, 1px 1px 1px white;
  }

  .led_off { 
    height: 20px;  
    width: 20px;
    border: solid 2px #505050;
    border-radius: 50%;  
    background: radial-gradient(circle at 30% 30%, red 20%, #8B0000);
    display: inline-block;
    margin: 2px;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

  .led_on { 
    height: 20px;  
    width: 20px; 
    border: solid 2px #505050;
    border-radius: 50%;  
    background: radial-gradient(circle, yellow 15%, orange 50%, red);
    display: inline-block;
    margin: 2px;
    box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -moz-box-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
    -webkit-shadow: -1px -1px 1px white, 1px 1px 1px #101010;
  }

</style>
</head>

<body>

<div id="header">
  <div id="cartouche" class="title">
  Name of the FPGA board
  </div>
</div>
<br>

<div id="panel" class="panel"><div class="prow"></div></div>

<script>
  // Access to the components of the UI.
  const cartouche = document.getElementById("cartouche");
  const panel     = document.getElementById("panel");

  // The current time stamp.
  var time_stamp = 0;

  // The input and output elements' ids.
  const input_ids = [];
  const output_ids = [];

  // The control functions.

  // Set the cartouche of the board.
  function set_cartouche(txt) {
    cartouche.innerHTML = txt;
  }

  // Add an element.
  function add_element(txt) {
    if (txt.includes('prow')) {
      // New panel row.
      panel.innerHTML += txt;
      return;
    }
    const prow = panel.lastElementChild;
    prow.innerHTML += txt;
    const element = prow.lastElementChild;
    // Depending of the kind of element.
    if (element.classList.contains('swset') ||
        element.classList.contains('btset') ||
        element.classList.contains('sliderset') ||
        element.classList.contains('textset') ) {
      // Input element.
      input_ids.push(element.id);
    } else {
      // Output element.
      output_ids.push(element.id);
    }
  }

  // Set the size of a canvas.
  function setCanvasSize(canvas) {
    // Get the DPR and size of the canvas
    const dpr = window.devicePixelRatio;
    const parentRect = canvas.parentElement.getBoundingClientRect();
    // console.log("parentRect=[" + parentRect.width + "," + parentRect.height + "]");

    // Set the "actual" size of the canvas
    canvas.width =  parentRect.width * dpr;
    canvas.height = parentRect.height * dpr;

    // Scale the context to ensure correct drawing operations
    canvas.getContext("2d").scale(dpr, dpr);

    // Set the "drawn" size of the canvas
    canvas.style.width =  `${parentRect.width}px`;
    canvas.style.height = `${parentRect.height}px`;
  }

  // Handler of sw change.
  function sw_change(sw) {
    // Get the set holding sw.
    const swset = sw.parentElement.parentElement;
    const bit = sw.dataset.bit;
    if (sw.checked) { 
      swset.dataset.value = swset.dataset.value | (1 << bit);
    }
    else {
      swset.dataset.value = swset.dataset.value & ~(1 << bit);
    }
    // console.log("sw value=" + swset.dataset.value);
  }

  // Handler of slider change.
  function slider_change(slider) {
    // Get the set holding slider.
    const sliderset = slider.parentElement;
    sliderset.dataset.value = slider.value;
    // console.log("slider value=" + sliderset.dataset.value);
  }

  // Set the aspect of a button to clicked.
  function bt_on(bt) {
    bt.innerHTML = '<i class="bt_on"></i>';
  }

  // Set the aspect of a button to not clicked.
  function bt_off(bt) {
    bt.innerHTML = '<i class="bt_off"></i>';
  }

  // Handler of button clicked.
  function bt_click(bt) {
    // Change the aspect of the button.
    bt_on(bt);
    // Get the set holding bt.
    const btset = bt.parentElement;
    const bit = bt.dataset.bit;
    // Update its value.
    btset.dataset.value = btset.dataset.value | (1 << bit);
  }

  // Handler of button released.
  function bt_release(bt) {
    // Change the aspect of the button.
    bt_off(bt);
    // Get the set holding bt.
    const btset = bt.parentElement;
    const bit = bt.dataset.bit;
    // Update its value.
    btset.dataset.value = btset.dataset.value & ~(1 << bit);
  }

  
  // Handler of the text submit
  function text_submit(form) {
     // Get the et holding the form.
     const textset = form.parentElement;
     // Update the value, suffixed with #{TO_COMPUTE} for telling it is a
     // text expression to compute.
     textset.dataset.value = form.elements.text.value + "#{TO_COMPUTE}"
  }


  // Switch a led on.
  function led_on(led) {
    led.classList.remove('led_off');
    led.classList.add('led_on');
  }

  // Switch a led off.
  function led_off(led) {
    led.classList.remove('led_on');
    led.classList.add('led_off');
  }
  
  // Update a led set.
  function ledset_update(ledset,value) {
    // Update the ledset value.
    ledset.dataset.value = value;
    // Update each led.
    // Get the individual leds.
    const leds = ledset.getElementsByTagName("i");
    const num = leds.length;
    // Set each led of the set.
    for(let i=0; i < num; ++i) {
      const val = (value >> i) & 1;
      if (val == 1) { led_on(leds[num-i-1]); }
      else          { led_off(leds[num-i-1]); }
    }
  }

  // Update a digit set.
  function digitset_update(digitset,value) {
    // Update the digiset value.
    digitset.dataset.value = value;
    // Update its display.
    const num = digitset.dataset.width;
    digitset.lastElementChild.innerHTML = String(value).padStart(num,"\u00A0");
  }

  // Update a hexadecimal set.
  function hexaset_update(hexaset,value) {
    // Update the digiset value.
    hexaset.dataset.value = value;
    // Update its display.
    const num = hexaset.dataset.width;
    hexaset.lastElementChild.innerHTML = Number(value).toString(16).padStart(num,'0');
  }

  // Update an oscilloscope.
  function scope_update(scope,value,new_time_stamp) {
    // Compute the advance in time.
    let diff_time_stamp = new_time_stamp - time_stamp;
    // Get the canvas.
    const canvas = scope.lastElementChild;
    // Shall we set up its size?
    let first = 0;
    if (scope.dataset.configured != 1) {
      // First time, so yes.
      setCanvasSize(canvas);
      scope.dataset.configured = 1;
      first = 1;
    }
    // Its size.
    const { width, height } = canvas.getBoundingClientRect();
    // Its context.
    const cxt = canvas.getContext("2d");
    // Get the properties of the scope.
    const min = Number(scope.dataset.min);
    const max = Number(scope.dataset.max);
    const pos = Number(scope.dataset.pos);
    const previous = Number(scope.dataset.previous);
    // console.log("min=" + min + " max=" + max);
    const toPx = function(val) { // Convert a percentage to x position.
      return (val*width)/100;
    }
    const toPy = function(val) { // Convert an input value to y position.
      return height - ((val-min) * height) / (max-min);
    }
    // Shall we restart the drawing?
    if (pos >= 100 || first == 1) {
      // Yes, clears the canvas.
      cxt.clearRect(0, 0, width, height);
      /* Draw the grid. */
      cxt.strokeStyle = "#b3b3b3";
      cxt.setLineDash([2,1]);
      cxt.lineWidth = 1;
      cxt.beginPath();
      for(let i=0; i<100; i += 10) {
         cxt.moveTo(toPx(0),  toPy((i*(max-min))/100+min));
         cxt.lineTo(toPx(100),toPy((i*(max-min))/100+min));
         cxt.moveTo(toPx(i),  toPy(min));
         cxt.lineTo(toPx(i),  toPy(max));
      }
      cxt.stroke();
      cxt.setLineDash([]);
      cxt.beginPath();
      for(let i=0; i<100; i+= 2) {
         cxt.moveTo(toPx(50-0.7), toPy((i*(max-min))/100+min));
         cxt.lineTo(toPx(50+0.7), toPy((i*(max-min))/100+min));
         cxt.moveTo(toPx(i),  toPy((max-min)/2 + min - 0.7*(max-min)/100));
         cxt.lineTo(toPx(i),  toPy((max-min)/2 + min + 0.7*(max-min)/100));
      }
      cxt.stroke();
      // Set the pen color for drawing the signal.
      cxt.strokeStyle = "yellow";
      cxt.lineWidth = 2;
      // Draw a single pixel.
      cxt.beginPath();
      cxt.moveTo(toPx(0), toPy(value));
      cxt.lineTo(toPx(0), toPy(value));
      cxt.stroke();
      /* Update the values. */
      scope.dataset.previous = value;
      scope.dataset.pos = 0;
    } else {
      // Go on, draw a line to the new position.
      // Set the pen color for drawing the signal.
      cxt.strokeStyle = "yellow";
      cxt.lineWidth = 2;
      // Draw a line to the new position.
      cxt.beginPath();
      cxt.moveTo(toPx(pos),   toPy(previous));
      cxt.lineTo(toPx(pos+diff_time_stamp), toPy(value)); 
      cxt.stroke();
      /* Update the values. */
      scope.dataset.previous = value;
      scope.dataset.pos = pos + diff_time_stamp;
    }
  }

  // Update a general display element.
  function element_update(element,value,new_time_stamp) {
    if(element.classList.contains('ledset'))  { ledset_update(element,value); }
    if(element.classList.contains('digitset')){ digitset_update(element,value); }
    if(element.classList.contains('signedset')){signedset_update(element,value);}
    if(element.classList.contains('hexaset')) { hexaset_update(element,value); }
    if(element.classList.contains('scope'))   { scope_update(element,value,new_time_stamp); }
  }


  // Synchronize with the HDLRuby simulator.
  function hruby_sync() {
    let xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
      // console.log("response=" + this.responseText);
      if (this.readyState == 4 && this.status == 200) {
        if (/^[0-9]+;[0-9]+:-?[0-9]/.test(this.responseText)) {
          // There is a real response.
          // Update the interface with the answer.
          const commands = this.responseText.split(';');
          // Get the new time stamp.
          let new_time_stamp = commands.shift();
          // console.log("new_time_stamp=" + new_time_stamp);
          // Process the other commands.
          for(command of commands) {
             const toks = command.split(':');
             element_update(document.getElementById(toks[0]),toks[1],new_time_stamp);
          }
          // Update the time stamp
          time_stamp = new_time_stamp
        }
      }
    };
    // Builds the action from the state of the input elements.
    let act = '';
    for(id of input_ids) {
      act += id + ':' + document.getElementById(id).dataset.value + ';';
    }
    // console.log("act=" + act);
    xhttp.open("GET", act, true);
    xhttp.overrideMimeType("text/plain; charset=x-user-defined");
    xhttp.send();
  }

  // First call of synchronisation.
  hruby_sync();

  // Moved to the Ruby constructor to allow setting the time intervals.
  // // Then periodic synchronize.
  // setInterval(function() { hruby_sync(); }, 100);

</script>

HTMLHEADER
<<-HTMLFOOTER
</body>
</html>
HTMLFOOTER
UI_response =
<<-HTMLRESPONSE
HTTP/1.1 200
Content-Type: text/plain

HTMLRESPONSE
@@http_ports =

The already used ports.

[]

Constants included from Hmissing

Hmissing::High, Hmissing::NAMES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Hmissing

#method_missing

Constructor Details

#initialize(name, http_port: 8000, refresh_rate: 100, &hdlruby_block) ⇒ Board

Create a new board named +name+ accessible on HTTP port +http_port+ and whose content is describe in +hdlruby_block+.


980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
# File 'lib/HDLRuby/ui/hruby_board.rb', line 980

def initialize(name, http_port: 8000, refresh_rate: 100,
               &hdlruby_block)
  # Set the name.
  @name = name.to_s
  # Set the refresh rate.
  @refresh_rate = refresh_rate.to_i
  # Tell the interface is to be built.
  @first = true
  # Check and set the port.
  http_port = http_port.to_i
  if (@@http_ports.include?(http_port)) then
    # Port already used, error.
    raise UIError.new("UI (http) port #{http_port} already in use.")
  end
  @http_port = http_port
  @@http_ports << @http_port
  # Create the server
  @server = TCPServer.new(@http_port)

  browse = Thread.new do
    url = "http://localhost:#{@http_port}"
    # Shall we try to open a page automatically:
    puts "Shall I open a new page on your browser for the GUI? [y/n]"
    ch = ""
    ch = STDIN.getch while !(ch =~ /[YyNn]/)
    unless ch =~ /[Yy]/ then
      puts "Please open the following url manually: #{url}"
      browse.kill
    end
    # Open the page if possible.
    sleep(0.2)
    case RUBY_PLATFORM
    when /darwin/
      Kernel.system("open",url)
    when /linux/
      Kernel.system("xdg",url)
    when /mingw|mswin/
      Kernel.system("start",url)
    else
      puts "Please open the following url on your browser: #{url}"
    end
  end

  # Create the running function.
  this = self
  Kernel.define_method(@name.to_sym) { this.run }
  # Initialize the list of board elements to empty.
  @elements = []
  @out_elements = []
  # Initialize the time stamp.
  @time_stamp = 0
  # And build the board.
  # Create the namespace for the program.
  @namespace = Namespace.new(self)
  # Build the program object.
  High.space_push(@namespace)
  pr = nil
  High.top_user.instance_eval do
    pr = program(:ruby, @name.to_sym) { }
  end
  @program = pr
  # Fill it.
  High.top_user.instance_eval(&hdlruby_block)
  High.space_pop
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class HDLRuby::High::Hmissing

Instance Attribute Details

#namespaceObject (readonly)

Returns the value of attribute namespace.


23
24
25
# File 'lib/HDLRuby/ui/hruby_board.rb', line 23

def namespace
  @namespace
end

Instance Method Details

#actport(*evs) ⇒ Object

Adds new activation ports.


1047
1048
1049
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1047

def actport(*evs)
  @program.actport(*evs)
end

#ascii(n, hport) ⇒ Object

Add a new ASCII element of +n+ columns attached to HDLRuby port +hport+.


1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1189

def ascii(n, hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  # Createthe ui component.
  @elements << ASCII.new(@elements.size,hport[1].type.width,hport[0])
  @out_elements << @elements[-1]
end

#bitmap(w, h, hport) ⇒ Object

Add a new bitmap element of width +w+ and height +h+ attached to HDLRuby port +hport+.


1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1203

def bitmap(w,h, hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  # Createthe ui component.
  @elements << BITMAP.new(@elements.size,w.to_i,h.to_i,hport[0])
  @out_elements << @elements[-1]
end

#bt(hport) ⇒ Object

Add a new button element attached to HDLRuby port +hport+.


1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1064

def bt(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.outport(hport)
  hport = hport.first
  # Create the UI component.
  @elements << BT.new(@elements.size,hport[1].type.width,:"#{hport[0]}=")
end

#compute(val) ⇒ Object

Compute a value.


1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1223

def compute(val)
  # Preprocess val to match Ruby syntax.
  val = "0" unless val
  val = val.gsub("%20"," ")
  # Replace the names by the corresponding ports read result.
  val = val.gsub(/([\._a-zA-Z0-9]+)/) do |str|
    if str[0] >= "a" && str[0] <= "z" then
      # Variable identifier, process it if recognized.
      RubyHDL.send(Regexp.last_match[1]).to_s rescue str
    else
      # Other token leave it as is.
      str
    end
  end
  # Compute.
  res = 0
  safe = $SAFE
  begin
    $SAFE = 2
    res = eval(val).to_i
  rescue SyntaxError => se
  rescue StandardError => se
  end
  $SAFE = safe
  return res
end

#digit(hport) ⇒ Object

Add a new digit element attached to HDLRuby port +hport+.


1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1137

def digit(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  sign = hport[1].type.signed?
  # Createthe ui component.
  @elements << DIGIT.new(@elements.size, 
          Math.log10(2**hport[1].type.width - 1).to_i + (sign ? 2 : 1),
          hport[1].type,
          hport[0])
  @out_elements << @elements[-1]
end

#hexa(hport) ⇒ Object

Add a new hexadecimal element attached to HDLRuby port +hport+.


1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1154

def hexa(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  # Createthe ui component.
  @elements << HEXA.new(@elements.size,
                        (hport[1].type.width-1)/4+1, hport[0])
  @out_elements << @elements[-1]
end

#hook(hport) ⇒ Object

Add a new hook element attached to HDLRuby port +hport+. NOTE: a hook element is not displayed on the board but can be used in the text expression.


1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1111

def hook(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  # Createthe UI component (invisible)
  @elements << HOOK.new(@elements.size,hport[1].type.width,hport[0])
  @out_elements << @elements[-1]
end

#led(hport) ⇒ Object

Add a new LED element attached to HDLRuby port +hport+.


1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1124

def led(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  # Createthe UI component.
  @elements << LED.new(@elements.size,hport[1].type.width,hport[0])
  @out_elements << @elements[-1]
end

#make_response(request) ⇒ Object

Generate a response to a request to the server.


1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1256

def make_response(request)
  # puts "request=#{request}"
  if (@first) then
    @first = false
    # First or re-connection, generate the UI.
    return UI_header + 
      "\n<script>\n" + 
      "// Then periodic synchronize.\n" + 
      "setInterval(function() { hruby_sync(); }, #{@refresh_rate});\n" + 
      "set_cartouche('#{@name}');\n" +
      @elements.map do |elem|
        "add_element('#{elem.to_html}');"
      end.join("\n") + 
      "\n</script>\n" + UI_footer
  else
    # This should be an AJAX request, process it.
    commands = request.split(";")
    commands.each do |command|
      # next unless command.include?(":")
      # id, val = command.split(":").map {|t| t.to_i}
      id, val = command.split(":",2)
      next unless val
      id = id.to_i
      if val[-1] == "=" then
        # This is an expression to compute.
        val = self.compute(val.chop)
      else
        val = val.to_i
      end
      self.update_port(id,val)
    end
    # And generate the response: an update of each board output element.
    return UI_response + "#{@time_stamp};" + 
      @out_elements.each.map do |e|
      # puts "resp=" + "#{e.id}:#{RubyHDL.send(e.hread)}"
      "#{e.id}:#{RubyHDL.send(e.hread)}"
    end.join(";")
  end
end

#rowObject

Add a new panel row.


1216
1217
1218
1219
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1216

def row()
  # Createthe ui component.
  @elements << PROW.new(@elements.size)
end

#runObject

Start the ui.


1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1297

def run
  # At first the ui is not running.
  @connected = false
  # Create the ui thread.
  @thread = Thread.new do
    while session = @server.accept
      # A request came, process it.
      request = session.gets
      verb,path,protocol  = request.split(' ')
      # puts "verb=#{verb} path=#{path} protocol=#{protocol}"
      if protocol === "HTTP/1.1"
        # The request is valid, generate the response.
        session.print self.make_response(path[1..-1])
        # And tell the ui has been connected.
        @connected = true
        # Then advance the time stamp.
        @time_stamp += 1
      else
        session.print 'Connection Refuse'
      end

      session.close
    end
  end
  # Wait for a first connection.
  sleep(0.1) while(!@connected)
end

#scope(hport) ⇒ Object

Add a new scope element attached to HDLRuby port +hport+.


1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1168

def scope(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.inport(hport)
  hport = hport.first
  width = hport[1].type.width
  if hport[1].type.signed? then
    min = -2**(width-1)
    max = 2**(width-1) - 1
  else
    min = 0
    max = 2**width - 1
  end
  # Createthe ui component.
  @elements << SCOPE.new(@elements.size, min, max, hport[0])
  @out_elements << @elements[-1]
end

#slider(hport) ⇒ Object

Add a new slider element attached to HDLRuby port +hport+


1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1076

def slider(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.outport(hport)
  hport = hport.first
  # Compute the min and max values.
  width = hport[1].type.width
  if hport[1].type.signed? then
    min = -2**(width-1)
    max = 2**(width-1) - 1
  else
    min = 0
    max = 2**width - 1
  end
  # Create the UI component.
  @elements << SLIDER.new(@elements.size,min,max,:"#{hport[0]}=")
end

#sw(hport) ⇒ Object

Add a new slide switch element attached to HDLRuby port +hport+.


1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1052

def sw(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.outport(hport)
  # Create the UI component.
  hport = hport.first
  @elements << SW.new(@elements.size,hport[1].type.width,:"#{hport[0]}=")
end

#text(hport) ⇒ Object

Add a new text input element attached to HDLRuby port +hport+


1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1097

def text(hport)
  if !hport.is_a?(Hash) or hport.size != 1 then
    raise UIError.new("Malformed HDLRuby port declaration: #{hport}")
  end
  # Create the HDLRuby program port.
  @program.outport(hport)
  hport = hport.first
  # Create the UI component.
  @elements << TEXT.new(@elements.size,hport[1].type.width,:"#{hport[0]}=")
end

#update_port(id, val) ⇒ Object

Update port number +id+ with value +val+.


1251
1252
1253
# File 'lib/HDLRuby/ui/hruby_board.rb', line 1251

def update_port(id,val)
  RubyHDL.send(@elements[id].hwrite,val)
end