見出し画像

『CPUの創りかた』TD4エミュレーターをつくってみた kintone

『CPUの創りかた』という本を知っていますか?

コンピューターに興味ある方、電子工作に興味ある方なら、書店で見かけたことがあるかもしれません。萌え系の表紙です。^^;

「これ、すごい名著です!!」

時代の流れがはやいIT業界で、20年近く前の書籍が初版から歳月を経て既に30刷以上増刷され、そして今や電子書籍になるなんて。まさに不朽の名著ですよね。

kintoneIoT部としては電子工作にも興味があるところですが、今回は書籍で作成する「TD4」(とりあえず動作するだけの4bitCPU)というCPUのエミュレーターをつくってみたくなりました。

本書自体にもWindows版のエミュレーターが掲載・公開されています。でも自分でつくってみたい!

で、ググってみると既にたくさんの先人の方たちが、エミュレーターを自作されていました。

Python。msakamoto-sf様ありがとうございます。

c。masami256様ありがとうございます。

JavaScript。seibe.jp様ありがとうございます。

皆様すばらしいアウトプットだと思います。大変参考になりました。
ありがとうございました。

私も続きます。もちろん私はkintoneで!!

kintoneでTD4

過去、kintoneの標準機能で加算器は作ったことがあります。

今回は標準機能だと厳しい感じなので、目下絶賛勉強中のJSカスタマイズでやってみました。で、なんとか動いた感じなのでシェアします!
kintoneのアプリテンプレート添付します。

サンプルデーターも併せてどうぞ。

レコード番号を含みますので、CSV読み込み時、一括更新のキーのチェックは外してください。またレコード詳細を編集で開いた直後は、まず「リセット」ボタンをクリックしてください。

アプリ解説

えっと、そもそも書籍ありきの話なので、どこまでが紹介できる範囲なのかわからないのと、ちょっとボリューム的にひとつのnote記事に詳細を書き上げるのは無理だなーと思ったので、ざくっとした解説でカンベンしてください。ご興味ある方はぜひ書籍で。^^;

アプリ詳細画面
本家の仕様にがんばって合わせています。^^;
以下画面は、書籍のサンプルプログラム「ラーメンタイマー」を入力したもの。アイコンはKUMA ICON様。ありがとうございます。

レコードを新規登録後、まずは「リセット」
アドレス0以降、命令をチェックボックスで指定していきます。

「クロック」を押すとプログラムのステップ実行が開始
今どこを実行中か「👈」が指し示してくれます。
また出力ポートにより「🔴🔴⚪🔴」みたいな感じでLEDっぽく点灯します。

「1Hz」をクリックすると1秒ごと、「10Hz」をクリックすると1秒に10回ペースでプログラムカウンタを更新します。

こんな感じでうごきます(画質よくないですが^^;)

フィールド詳細
フィールド名(フィールドコード):レジスタA:文字列(1行)
フィールド名(フィールドコード):レジスタB:文字列(1行)
フィールド名(フィールドコード):キャリーフラグ:文字列(1行)
フィールド名(フィールドコード):プログラムカウンタ:文字列(1行)

スペース:要素ID:spaceManual
スペース:要素ID:spaceAuto

フィールド名(フィールドコード):インプット:チェックボックス
i3,i2,i1,i0

フィールド名(フィールドコード):入力ポート:文字列(1行)
自動計算する

IF(CONTAINS(インプット,"i3"),1,0) &
IF(CONTAINS(インプット,"i2"),1,0) &
IF(CONTAINS(インプット,"i1"),1,0) &
IF(CONTAINS(インプット,"i0"),1,0)

フィールド名(フィールドコード):出力ポート:文字列(1行)

フィールド名(フィールドコード):アウトプットLED:文字列(1行)
自動計算する

IF(出力ポート="0000","⚪⚪⚪⚪",
IF(出力ポート="0001","⚪⚪⚪🔴",
IF(出力ポート="0010","⚪⚪🔴⚪",
IF(出力ポート="0011","⚪⚪🔴🔴",
IF(出力ポート="0100","⚪🔴⚪⚪",
IF(出力ポート="0101","⚪🔴⚪🔴",
IF(出力ポート="0110","⚪🔴🔴⚪",
IF(出力ポート="0111","⚪🔴🔴🔴",
IF(出力ポート="1000","🔴⚪⚪⚪",
IF(出力ポート="1001","🔴⚪⚪🔴",
IF(出力ポート="1010","🔴⚪🔴⚪",
IF(出力ポート="1011","🔴⚪🔴🔴",
IF(出力ポート="1100","🔴🔴⚪⚪",
IF(出力ポート="1101","🔴🔴⚪🔴",
IF(出力ポート="1110","🔴🔴🔴⚪",
IF(出力ポート="1111","🔴🔴🔴🔴",
"⚫⚫⚫⚫"
))))))))))))))))

フィールド名(フィールドコード):アドレス0:チェックボックス
d7,d6,d5,d4,d3,d2,d1,d0
※アドレス0~アドレスFまで繰り返し(コピペ作成)

フィールド名(フィールドコード):アドレス00:文字列(1行)
自動計算する
※アドレス00~アドレス15まで繰り返し(コピペ作成)

IF(CONTAINS(アドレス0,"d7"),1,0) &
IF(CONTAINS(アドレス0,"d6"),1,0) &
IF(CONTAINS(アドレス0,"d5"),1,0) &
IF(CONTAINS(アドレス0,"d4"),1,0) &
IF(CONTAINS(アドレス0,"d3"),1,0) &
IF(CONTAINS(アドレス0,"d2"),1,0) &
IF(CONTAINS(アドレス0,"d1"),1,0) &
IF(CONTAINS(アドレス0,"d0"),1,0)

フィールド名(フィールドコード)s00:文字列(1行)
※s00~s15まで繰り返し(コピペ作成)

ラベル:アドレス00
※アドレス00~アドレス15まで繰り返し(コピペ作成)

アプリの説明にアセンブラのニーモニックを記載しています。

JSプログラム

(function() {
  'use strict';
  
  let timer;

  kintone.events.on([
                      'app.record.create.show', 
                      'app.record.edit.show'
                    ], buttonSet);
 
  function buttonSet(event) {

    //クロックボタンを設置
    const clockButton = document.createElement('button');
    clockButton.id = 'clock_button';
    clockButton.innerText = 'クロック';
  
    //リセットボタンを設置
    const resetButton = document.createElement('button');
    resetButton.id = 'reset_button';
    resetButton.innerText = 'リセット';

    //1Hzボタンを設置
    const clockButton1Hz = document.createElement('button');
    clockButton1Hz.id = 'clock_1Hz';
    clockButton1Hz.innerText = '1Hz';

    //10Hzボタンを設置
    const clockButton10Hz = document.createElement('button');
    clockButton10Hz.id = 'clock_10Hz';
    clockButton10Hz.innerText = '10Hz';

    //クロックボタンを押した
    clockButton.onclick = function() {
      clock();
    };
    kintone.app.record.getSpaceElement('spaceManual').appendChild(clockButton);

    //リセットボタンを押した
    resetButton.onclick = function() {
      clockButton1Hz.disabled = false;
      clockButton10Hz.disabled = false;
      clearInterval(timer);
      reset();
    };
    kintone.app.record.getSpaceElement('spaceManual').appendChild(resetButton);

    //1Hzボタンを押した
    clockButton1Hz.onclick = function() {
      clockButton1Hz.disabled = true;
      clockButton10Hz.disabled = true;
      timer = window.setInterval(
      function() {
        clock();
      },1000);
    };
    kintone.app.record.getSpaceElement('spaceAuto').appendChild(clockButton1Hz);

    //10Hzボタンを押した
    clockButton10Hz.onclick = function() {
      clockButton1Hz.disabled = true;
      clockButton10Hz.disabled = true;
      timer = window.setInterval(
      function() {
        clock();
      },100);
    };
    kintone.app.record.getSpaceElement('spaceAuto').appendChild(clockButton10Hz);

    return event;
  }
  
  function clock() {
  
    let ip = 0;                  
    let opim = '00000000';
    let op = '0000';
    let im = '0000';
    let im10 = 0;
    let reg10 = 0;
    let imReg10 = 0;
    let reg2 = 0;
    let cf = 0;
  
    const rec = kintone.app.record.get();
  
    ip = parseInt(rec.record['プログラムカウンタ'].value, 10);
    cf = parseInt(rec.record['キャリーフラグ'].value, 10);
  
    //フェッチ
    switch (ip) {
      case 0:
        opim = rec.record['アドレス00'].value;
        break;
      case 1:
        opim = rec.record['アドレス01'].value;
        break;
      case 2:
        opim = rec.record['アドレス02'].value;
        break;
      case 3:
        opim = rec.record['アドレス03'].value;
        break;
      case 4:
        opim = rec.record['アドレス04'].value;
        break;
      case 5:
        opim = rec.record['アドレス05'].value;
        break;
      case 6:
        opim = rec.record['アドレス06'].value;
        break;
      case 7:
        opim = rec.record['アドレス07'].value;
        break;
      case 8:
        opim = rec.record['アドレス08'].value;
        break;
      case 9:
        opim = rec.record['アドレス09'].value;
        break;
      case 10:
        opim = rec.record['アドレス10'].value;
        break;
      case 11:
        opim = rec.record['アドレス11'].value;
        break;
      case 12:
        opim = rec.record['アドレス12'].value;
        break;
      case 13:
        opim = rec.record['アドレス13'].value;
        break;
      case 14:
        opim = rec.record['アドレス14'].value;
        break;
      case 15:
        opim = rec.record['アドレス15'].value;
        break;
      default:
        window.alert('fetch ERR');
    }
  
    op = opim.substr(0,4);
    im = opim.substr(4,4);

    //デコード+実行+プログラムカウンタ
    switch (op) {
      case '0011':  // MOV A, Im
        rec.record['レジスタA'].value = im;
        cf = 0;
        ip++;
        break;
      case '0111':  // MOV B, Im
        rec.record['レジスタB'].value = im;
        cf = 0;
        ip++;
        break;
      case '0001':  // MOV A, B
        rec.record['レジスタB'].value = rec.record['レジスタA'].value;
        cf = 0;
        ip++;
        break;
      case '0100':  // MOV B, A
        rec.record['レジスタA'].value = rec.record['レジスタB'].value;
        cf = 0;
        ip++;
        break;
      case '0000':  // ADD A, Im
        im10 = parseInt(im, 2);  // 2進→10進(数値型)
        reg10 = parseInt(rec.record['レジスタA'].value, 2);

        imReg10 = im10 + reg10;
        if (imReg10 > 15) {
          imReg10 = 0;
          cf = 1;
        } else {
          cf = 0;
        }

        reg2 = (imReg10).toString(2);  // 10進→2進(文字列型)
        reg2 = ('0000' + reg2).slice(-4);  // 右から4文字切り出し
        rec.record['レジスタA'].value = reg2;
        ip++;
        break;
      case '0101':  // ADD B, Im
        im10 = parseInt(im, 2);
        reg10 = parseInt(rec.record['レジスタB'].value, 2);
        
        imReg10 = im10 + reg10;
        if (imReg10 > 15) {
          imReg10 = 0;
          cf = 1;
        } else {
          cf = 0;
        }

        reg2 = (im10 + reg10).toString(2);
        reg2 = ('0000' + reg2).slice(-4);
        rec.record['レジスタB'].value = reg2;
        ip++;
        break;
      case '0010':  // IN A
        rec.record['レジスタA'].value = rec.record['入力ポート'].value;
        cf = 0;
        ip++;
        break;
      case '0110':  // IN B
        rec.record['レジスタB'].value = rec.record['入力ポート'].value;
        cf = 0;
        ip++;
        break;
      case '1011':  // OUT Im
        rec.record['出力ポート'].value = im;
        cf = 0;
        ip++;
        break;
      case '1001':  // OUT B
        rec.record['出力ポート'].value = rec.record['レジスタB'].value;
        cf = 0;
        ip++;
        break;
      case '1111':  // JMP Im
        im10 = parseInt(im, 2);
        ip = im10;
        cf = 0;
        break;
      case '1110':  // JNC Im
        if (cf === 0) {
          im10 = parseInt(im, 2);
          ip = im10;
        } else {
          ip++;
        } 
        cf = 0;
        break;
      default:
        window.alert('decode ERR');
    }
 
    //プログラムカウンタ目印
    rec.record['s00'].value = '';
    rec.record['s01'].value = '';
    rec.record['s02'].value = '';
    rec.record['s03'].value = '';
    rec.record['s04'].value = '';
    rec.record['s05'].value = '';
    rec.record['s06'].value = '';
    rec.record['s07'].value = '';
    rec.record['s08'].value = '';
    rec.record['s09'].value = '';
    rec.record['s10'].value = '';
    rec.record['s11'].value = '';
    rec.record['s12'].value = '';
    rec.record['s13'].value = '';
    rec.record['s14'].value = '';
    rec.record['s15'].value = '';

    switch (ip) {
      case 0:
        rec.record['s00'].value = '👈';
        break;
      case 1:
        rec.record['s01'].value = '👈';
        break;
      case 2:
        rec.record['s02'].value = '👈';
        break;
      case 3:
        rec.record['s03'].value = '👈';
        break;
      case 4:
        rec.record['s04'].value = '👈';
        break;
      case 5:
        rec.record['s05'].value = '👈';
        break;
      case 6:
        rec.record['s06'].value = '👈';
        break;
      case 7:
        rec.record['s07'].value = '👈';
        break;
      case 8:
        rec.record['s08'].value = '👈';
        break;
      case 9:
        rec.record['s09'].value = '👈';
        break;
      case 10:
        rec.record['s10'].value = '👈';
        break;
      case 11:
        rec.record['s11'].value = '👈';
        break;
      case 12:
        rec.record['s12'].value = '👈';
        break;
      case 13:
        rec.record['s13'].value = '👈';
        break;
      case 14:
        rec.record['s14'].value = '👈';
        break;
      case 15:
        rec.record['s15'].value = '👈';
        break;
      default:
        window.alert('ip ERR');
    }

    rec.record['プログラムカウンタ'].value = ip;
    rec.record['キャリーフラグ'].value = cf;

    kintone.app.record.set(rec);
  }
  
  function reset() {

    const rec = kintone.app.record.get();
    
    rec.record['レジスタA'].value = '0000';
    rec.record['レジスタB'].value = '0000';
    rec.record['プログラムカウンタ'].value = 0;
    rec.record['キャリーフラグ'].value = 0;
    rec.record['出力ポート'].value = '0000';
    rec.record['s00'].value = '👈';
    rec.record['s01'].value = '';
    rec.record['s02'].value = '';
    rec.record['s03'].value = '';
    rec.record['s04'].value = '';
    rec.record['s05'].value = '';
    rec.record['s06'].value = '';
    rec.record['s07'].value = '';
    rec.record['s08'].value = '';
    rec.record['s09'].value = '';
    rec.record['s10'].value = '';
    rec.record['s11'].value = '';
    rec.record['s12'].value = '';
    rec.record['s13'].value = '';
    rec.record['s14'].value = '';
    rec.record['s15'].value = '';

    kintone.app.record.set(rec);
  }
  
})();

ポイント
イベントは、レコード詳細画面のレコード追加イベント、レコード編集イベントで発火します。

クロックの表現は、前回、光るクリスマスツリーアプリをつくってみた kintone で利用したwindows.etInterval命令を使っています。

一応、CPUの動作原理に合わせて、フェッチのswitchで命令をとりだし、
デコード+実行+プログラムカウンタはまとめて1つのswitchで処理しています。

kintoneのフィールドは直接添え字が使えないので、配列は使わず、アドレス16個分のフィールドをベタにswitchで処理しています。^^;

プログラムカウンタ目印については、機能としてはなくてもよいのですが、今どこを実行してるのかは目で見て確認できたほうが面白いので、プログラムカウンタ(インストラクションポインタ)に合わせて、「👈」で指し示すようにしてみました。

大晦日

今日は大晦日。せっかくですので今日の夜はサンプルで作成したラーメンタイマー使って、ミニどん兵衛を作ってみることにします。わー実用的!!

いつものごとく、とりあえず私の環境でうごいたもので、JSのプログラムもまだまだなので不具合等あるかもしれません。もしなにかあれば教えてください!

それでは良いお年を!
来年もよろしくおねがいします!!


TD4エミュレーター更新履歴
2021.12.31 v1.0 リリース
2022.01.01 v1.1 コメント追加
2022.01.02 v1.2 コメント修正

この記事が気に入ったらサポートをしてみませんか?