『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行)
自動計算する
フィールド名(フィールドコード):出力ポート:文字列(1行)
フィールド名(フィールドコード):アウトプットLED:文字列(1行)
自動計算する
フィールド名(フィールドコード):アドレス0:チェックボックス
d7,d6,d5,d4,d3,d2,d1,d0
※アドレス0~アドレスFまで繰り返し(コピペ作成)
フィールド名(フィールドコード):アドレス00:文字列(1行)
自動計算する
※アドレス00~アドレス15まで繰り返し(コピペ作成)
フィールド名(フィールドコード)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 コメント修正
この記事が気に入ったらサポートをしてみませんか?