クソ雑魚エンジニアのメモ帳

学んだことを書くところ

v-kansai#4 に参加しました!

こんばんは。最近物欲がないmorifujiです。

昨日3/20の夜に、v-kansaiの第4回が大阪で開催されたので参加レポートを書かされた書きました!

vuekansai.connpass.com

誰だお前

  • atma株式会社で働いてるクソザコエンジニアです。
  • 直近だとnodejsでスクレイピングしてます。
  • vueに関係する業務だとvuejs/nuxtjs/netlify結構使います。
  • nuxtよりごりごりvuejsかくタイプです。

civiluo.co.jp

感想

Nuxt.jsとnetlifyが優しかった話(遅刻したので正確なタイトルわかりません 😭)

netlifyに関しては、開発者への優しさに溢れてますよねー。

yarn.lockがあったら勝手にyarnしてくれたりとか、ブランチごとにpreviewのサイトが建てられていたりとかA/Bテストやってくれたりとか画像を最適化してくれたりとか。全部GUIでぽちぽちするだけなのでいいですよねー。

光があれば闇もあるということで優しくない(むしろ辛い)ところを自分なりに考えてみました

  • Trailing slashの設定がない
  • netlify functionsのドキュメントが少ない(lambdaのドキュメントを読めということでしょうが)

Nuxt.js のディレクト

nuxtのディレクトリについて逐一丁寧に説明していただきました。

nuxtって、すでにディレクトリ構成が洗練されているものなので、小〜中規模のPJまではディレクトリ構成をそのまま使えて幸せですよね。vuejsだと個々のPJによってディレクトリ構成がオレオレ構成に変えられていたりする(し、ディレクトリ構成のREADMEがないことが多い)。

強欲なエンジニアというパワーワードが聞けて面白かった

余談ですが、登壇者の方どこかでお見かけしたと思ってましたがPHPカンファレンスにいらしゃった方なんですね(一度だけ前参加した)。

あと。そのあとのMCの人との会話の中で、 page単位のビルドだと、pageを移動するたびに同じcomponentやjsを再度読み込むが、全体のビルドだと、初期ロードの一度で読むのでオーバーヘッドがない、みたいな話が出てたが少しわからなかった。誰か詳しく教えて。

speakerdeck.com

揮発性の高いcomponentを作る

twitterでよく見る人の発表でした。

揮発性の高いcomponent=ダイアログとかツールチップとか

グローバルメソッドにする発想がなかったので目から鱗だった。

confirm()と同じようなノリでかけるのは面白い

// confirm()と似てる
const isAgreed1 = await this.$dialog("hogehoge", "warning", options)
const isAgreed2 = confirm("hogehoge")

メリットとしてあげられたのは以下の通り

  • メンテの向上
  • テンプレートを軽く
  • stateやローカルdataを圧迫しない

テンプレートを軽くできるのは本当に嬉しい。使うたびにコンポーネントをimportしてhandlerつけてって作業は本当に煩わしい。

ただ、ダイアログというと状態管理が必要だろうし、個人的にはtypescriptが欲しくなりそうな内容だった。まあけどそこまでするならルートコンポーネントにダイアログ配置してvuexで表示非表示の制御がベターなんだろうなあと思った。

speakerdeck.com

capacitor

capacitorの紹介でした。

capacitorとcordovaの違いとしてあげられたのは以下の通り。

capacitor Cordova 何がどう変わるか
ビルド時、アプリの必要ファイル群全てを吐き出す 必要ファイルのwwwのみ capacitorだとビルド時間が早い/gitの管理が楽
コアプラグインがあり、本体のバージョンに常に追随する コアプラグインはない capacitorはバージョンを上げるときに、依存関係をcordovaに比べあまり気にしなくて良い
プラグインはまだあまりなさそう 4380個のプラグイン(昨日夜時点) プラグインはcordovaの方が多い

まだベータ版なのでコミュニティは大きくはないがめっちゃ期待できますねー

VueとAWSAppSyncで始めるチャットアプリ開発

AWSAppSyncでgraphQLを使ってチャットアプリを作った事例の紹介

AWSのAppSyncはあまり知らなかったので今回のイベントで一番興味があったスライドですね。

queryとmutationの概念はわかったけどResolverがわからず放置した記憶。。

気になった点

  • 必要なattributeだけ取得できるっていうのは本当に嬉しい点と同時にパフォーマンスの調整がサーバー側でできるのかどうかが少し心配。
    • クエリとかmutationの定義とかで制御できるのかな?誰か教えて
  • エンドポイントが一つだからログは少し見にくい?
    • cloudwatchにログを吐くことはできるからcloudwatchでごにょごにょすれば可能とのこと
  • AppSyncから外部エンドポイント叩ける?
    • 無理とのこと。やるなら、lambdaを用意して、AppSync=>lambda=>外部エンドポイントとのこと。
    • このスライドだと直接叩いているようだけどどうなのだろう? 🧐

ちらりと映るbulmaがオシャレだった。

www.slideshare.net

NuxtをLightsailとGAEに デプロイした話

lightsailでdockerを使ってnuxtを建てていたが、GCPに移行した話。

lightsailとdocker/dockerswarmって相性どうなんだろう?awsでdocker使われるならecsなイメージだったので驚きでした。料金面でいうと天地の差があるのでもともとはlightsal選んだんですかね、

GCPはコンソールが優しくていいですよねー、感動 😭

slides.com

Web猫ブログに型安全を

nuxtの2.4.0からtypescriptのサポートを始めたので、typescriptを導入した話。

弊社のとあるnuxtのPJ(ちょうどバージョン2.4.0)でも規模が大きくなりそうなのでtypescriptを導入しないといけないという状態だったため非常に勉強になりました。

typescript非対応なライブラリを使うとしんどそうだが大丈夫そうとのこと

contentfulというサービスもスライドにちらっと映ってたけど、よさげ。CMS界隈あまり知りませんがコンテンツをjsonで管理しやすいようにする流れがあるんですかね?

master.d2ezecqbj32kby.amplifyapp.com

AWS Lambdaで Vue.js SSRしてみた

npmプラグインvue-server-renderer)を使えばnuxtなしでSSRができるという話。

ssrならnuxt。nuxtならssrというレベルでnuxtが有名で、その選択肢を忘れていた。。

公式ガイドがあって優しい。けどユニバーサルなコードを書くために気をつける点が多くて難易度はnuxtに比べて高そう、以下抜粋。

  • カスタムディレクティブがそのままフロントに返されるとエラーになる
  • サーバーでの実行を考慮していないライブラリはSSRでエラー(例えばwindowみたいな変数使うライブラリとか)

vuejsでMVVM(タイトル未詳)

規模が大きくなるとmvvmの形には自然となりますよね。ただ意識はあまりしていなかったので必要性を再認識しました。

ionic/vue(タイトル未詳)

まだベータ版ですがionicがvuejsに対応した話。

あまりionic触らないので少しわからなかった 😇

speakerdeck.com

まとめ

初のvuejsのイベントでしたが結構vuejs周りのエコシステム(AppSync/vue-server-renderer)の勉強になりました。余裕あればLTやりたい!

登壇者の方ありがとうございました!また参加させていただきます!

もうprettierで消耗したくない人へのvueでのeslint設定

タイトル通り。vscodeとvuejsを使ってのlintの設定をprettierなしでやってみた。

tl;dr

対象ファイルとpackage.jsonと.eslintrc.jsの対応表👇

構文チェック・自動フォーマット
対象ファイル
npmライブラリ(package.json) .eslintrc.jsでのextends
.vue(<template>) eslint-plugin-vue plugin:vue/recommended
.vue(<script>) eslint eslint:recommended
. js eslint eslint:recommended
yarn add -D eslint eslint-plugin-vue
module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }
}
{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
          "language": "vue",
          "autoFix": true,
        },
   ],
    "eslint.autoFixOnSave": true,
}

環境

とあるプロジェクトでnuxt+vuejsを使って実装をしていた時の話。開発初期段階だったのもありnuxtが2.4をリリースしたというのでアップデートしてみた。しかし急にeslintとprettierの構文チェック・自動フォーマットが効かなくなった。。。。

開発環境はvscode

package.jsonのパッケージはだいたいこんな感じ。

  • vue@2.6.6
  • nuxt@2.4.3
  • babel-eslint@7.2.3
  • eslint@4.19.1
  • eslint-config-standard@10.2.1
  • eslint-loader@1.9.0
  • eslint-plugin-html@3.1.1
  • eslint-plugin-import@2.7.0,
  • eslint-plugin-node@5.1.1,
  • eslint-plugin-promise@3.5.0,
  • eslint-plugin-standard@3.0.1,
  • eslint-plugin-vue@5.2.2
  • eslint-config-prettier@4.0.0
  • eslint-plugin-prettier@3.0.1

そこで、再び構文チェック・自動フォーマットが効くように半日頑張ったが無理だった。半日溶けた。

問題点

  1. eslint系が12種と多い、多すぎる😇😇😇😇😇、どれが何の役割を果たしているかわからない
  2. prettier・veturも絡んでいる
  3. 依存関係がわからない(vscodeのconfig.json.eslintrc.jsにもprettierがある。)
  4. 元々の設定もどこかしらのドキュメントを参考につぎはぎしていたので何の設定をどこで制御できるのかわからない

👉カオス味を減らしたい

👉👉prettierはもういらないのでは??

👉👉👉prettierをなくして最低限のeslintによる構文チェック・自動フォーマットを設定する

手順

僕の.eslintrc.jsがいつまでも正しいかどうかわからないので記録を残しておく。苦しんで覚えたい人は再現してほしい。おれのしかばねをこえてゆけ💀💀💀

.vueの構文チェックその1

まずは自動フォーマットはひとまず無視して、構文チェックができるようにする。

eslint公式にはvuejsに関する記述はなかったが、 eslint-plugin-vueには Official ESLint plugin for Vue.jsという記載があったのでこれを信じる。

これだけでいいみたい。

  • eslint
  • eslint-plugin-vue

https://vuejs.github.io/eslint-plugin-vue/user-guide/#installation

以下は不要なもの。多すぎ。使っている人はいますぐpackage.jsonから消し去ろう

  • eslint-config-standard@10.2.1
  • eslint-loader@1.9.0
  • eslint-plugin-html@3.1.1
  • eslint-plugin-import@2.7.0,
  • eslint-plugin-node@5.1.1,
  • eslint-plugin-promise@3.5.0,
  • eslint-plugin-standard@3.0.1,
  • eslint-config-prettier@4.0.0
  • eslint-plugin-prettier@3.0.1

あとは .eslintrc.jsの設定だけ。これでOK

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    // 'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

https://vuejs.github.io/eslint-plugin-vue/user-guide/#configuration

この段階ではまだvscodeでの自動フォーマットは効いていないはず。落ち着いてまずは構文チェックができるかを確認

# srcフォルダにvueファイルがある前提

$ eslint --ext .js,.vue src
# または
$ eslint "src/**/*.{js,vue}"

構文チェックがうまく走るとおそらくこんな表示になる。

$ yarn run eslint "pages/**/*.{js,vue}"
....
....
✖ 70 problems (21 errors, 49 warnings)
  13 errors and 49 warnings potentially fixable with the `--fix` option.

出力からわかると思うが、--fixをつけることでフォーマットすることができる。

このことからeslintを用いることで、vueファイルの構文チェック・自動フォーマットができることが確認できた。

vscode上での自動フォーマット

こんどはCmd + Sft + Psettingと打ち、Open Settings(JSON)を選択。

ここで、vscode上でeslint構文チェック・自動フォーマットができるように以下を設定

{
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "vue",
      "autoFix": true,
    },
  ],
  "eslint.autoFixOnSave": true,
}

このことからvalidate=構文チェックかつautoFixOnSave=自動フォーマットなのがわかる

試しに、.vueファイルのtemplate内のインデントをぐちゃぐちゃにするとエラーが出て、Cmd + Sを何回か押すと自動フォーマットができることが確認できる。

しかしまだ不完全のようである。.vue<script>タグの中が全くフォーマットされていない

.vueの構文チェックその2

.eslintrc.jsを振り返り、以下の行をコメントアウト

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

すると、例えば以下のvueファイルで自動フォーマットをするとconsole.log("hoge");;;;;;;が自動で消えるようになる

<template>
  <div>
    <p>aaaa</p>
  </div>
</template>

<script>
export default {
  created() {
-      console.log("hoge");;;;;;
+   console.log("hoge");
          const    a     =     "A"
    var b = (4+(1-1))
    let c = [  1  ,  2 ,  3]


    const d = "zzzzzzzz"
  }
}
</script>

🎊🎊🎊おめでとう🎊🎊🎊。ここまで来ればeslintの設定はほとんど終わり。だけどscriptの中はまだまだ混沌としているので、あとでカスタマイズ。

rulesをカスタマイズ

<template>のカスタマイズ

今までの内容を見るに、vueファイルの<template>の構文チェックを行なっているのは eslint-plugin-vueであり、<template>eslint-plugin-vue独自のルールが適用されている。

独自のルールはここにまとめられている。今回の設定(以下ファイル)では、 'plugin:vue/recommended'として導入されているので、Priority A: Essential (Error Prevention)Priority B: Strongly Recommended (Improving Readability)の内容も含んでおり、一通りvuejs公式が推奨するルールはだいたい入っている。

module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',     // これ
    'plugin:vue/recommended'
  ],
  rules: {
  }
}

'plugin:vue/recommended'に入っていないルールであっても自分で追加することができる。

例えば筆者は以下の部分をカスタマイズした。

  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
  }

簡単にいうと、2はエラー。このルールを厳しく適用する

html-closing-bracket-newlineは、 'plugin:vue/recommended'には含まれているがカスタマイズしないと以下の構文のpタグがOKになってしまう。

<template>
  <div>
    <p
      hoge="#"
      huga="%%%"
      >
      aaaa
    </p>
  </div>
</template>

これは個人的には気に入らないので、"multiline": "never"という設定にした。

<template>
  <div>
    <p
      hoge="#"
-     huga="%%%"
-     >
+     huga="%%%">
      aaaa
    </p>
  </div>
</template>

注意点として、自動フォーマットができないルールもいくつかある。ルールの一覧画面に🔧マークがなければ自動フォーマットができないので注意しよう、

<script>をカスタマイズ

これが思ったよりしんどい。

これも今までの内容を見るに、<script>の構文チェックを行なっているのはeslintである。eslintのルールはこちらにある。100個以上あってなかなかしんどいが、一通り目を通すと非常に勉強になるのでみなさんにも読んでほしい。

今回は'eslint:recommended',として導入しており、ルール一覧画面では✔️マークがあるルールは全て含まれている。(こちらも注意点として、自動フォーマットができないルールもいくつかある。ルールの一覧画面に🔧マークがなければ自動フォーマットができない。)

ただし、recommendedでは先ほど出てきた構文でも特に何もエラー・警告を吐かない。ツッコミどころしかないのだが。。

          const    a     =     "A"
    var b = (4+(1-1))
    let c = [  1  ,  2 ,  3]


    const d = "zzzzzzzz"

これをどう直すかは人次第なので一意見として僕のrulesをいかに貼ります。

  rules: {
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }

ちなみに2は先ほど出てきたが、10もここで出てきた。1は警告(エラーではない)、0は許可。

例えばこのルールで先ほどの構文を自動フォーマットするとこうなる。幸せ。

    const a = 'A'
    let b = 4+(1-1)
    let c = [ 1, 2, 3]

    const d = 'zzzzzzzz'

まとめ。

prettierの設定に疲れた人はやって見ると良い。

対象ファイルとpackage.jsonと.eslintrc.jsの対応表👇

構文チェック・自動フォーマット
対象ファイル
npmライブラリ(package.json) .eslintrc.jsでのextends
.vue(<template>) eslint-plugin-vue plugin:vue/recommended
.vue(<script>) eslint eslint:recommended
. js eslint eslint:recommended
yarn add -D eslint eslint-plugin-vue
module.exports = {
  plugins: [
    "vue"
  ],
  extends: [
    'eslint:recommended',
    'plugin:vue/recommended'
  ],
  rules: {
    // タグの最後で改行しないで
    "vue/html-closing-bracket-newline": [2, {"multiline": "never"}],
    // 不要なカッコは消す
    "no-extra-parens": 1,
    // 無駄なスペースは削除
    "no-multi-spaces": 2,
    // 不要な空白行は削除。2行開けてたらエラー
    "no-multiple-empty-lines": [2, {"max": 1}],
    // 関数とカッコはあけない(function hoge() {/** */})
    "func-call-spacing": [2, "never"],
    // true/falseを無駄に使うな
    "no-unneeded-ternary": 2,
    // セミコロンは禁止
    "semi": [2, "never"],
    // 文字列はシングルクオートのみ
    "quotes": [2, "single"],
    // varは禁止
    "no-var": 2,
    // jsのインデントは2
    "indent": [2, 2],
    // かっこの中はスペースなし!違和感
    "space-in-parens": [2, "never"],
    // コンソールは許可
    "no-console": 0,
    // カンマの前後にスペース入れる?
    "comma-spacing": 2,
    // 配列のindexには空白入れるな(hogehoge[ x ])
    "computed-property-spacing": 2,
    // キー
    "key-spacing": 2,
    // キーワードの前後には適切なスペースを
    "keyword-spacing": 2,
  }
}
{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        {
          "language": "vue",
          "autoFix": true,
        },
   ],
    "eslint.autoFixOnSave": true,
}

おまけ

eslintのruleと死闘を繰り広げてるときに投げられた無慈悲なメッセージ

f:id:Kouchannel55:20190221202322p:plain

ESLintを入れただけでは今までのおまえのコードは何一つ綺麗にならない。おまえはメキシコの夕日よりも赤いコードを見ることになるだろう。

https://qiita.com/khsk/items/0f200fc3a4a3542efa90

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装6章)

こんばんは。10日ぶりの更新です。

時間が取れなくて前回からだいぶ遅くなった :sweat_drops:

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3

vol.4

vol.5

vol.6 いまここ

6章 アセンブラ

理論

この章ではアセンブリ言語アセンブラによって機械語に変換できるようにする。

アセンブリ言語

LOAD R3 7などの人間が読みやすい言語。

機械語

バイナリである。先ほどのLOAD R3 7は、ハードウェアの論理設計上で、

LOADは最初の8ビット。R3(レジスタ)は次の8ビット。7(アドレス)を残りの16ビットで表現するというようなルールが決められており、それに従って出力されたバイナリ

シンボル

アセンブリ言語上でメモリアドレスを間接的に指定できる変数。

任意のシンボルは、どこかのアドレスの位置を参照している

利用方法としては一般的に以下の2種類がある

  • ラベル
    • LOOPなど。gotoやjmpで利用するためのラベル、タグ。この行の次の行のアドレスを指し示す。この行自体は機械語に出力されない。
  • 変数
    • count=0i=1のように利用できる。変数はRAMと混合しないようにメモリのある地点から格納される。

シンボル解決

アセンブラは、シンボルを使わない場合だとルールに従ってバイナリに変換するだけで良い。しかし

、シンボルを利用する場合はシンボルが表すアドレスを全て保存しシンボルが利用されている箇所全てを置換する必要がある。

理論(Hack)

アセンブリ言語

各行は、以下のうちのどれかで構成される

  • A命令またはC命令の命令
  • シンボル
  • 意味のない行

また、コメントは //で書き、その行のそれ以降の文字は全てアセンブラに無視される

定義済みシンボル

R0...R15THISARGSは定義済みシンボル。

ラベル RAMアドレス
R0 0
R1 1
... ...
R15 15
SP 0
LCL 1
ARGS 2
THIS 3
THAT 4

実装

アセンブリを実装しなければならない。

2段階で実装する。

  1. シンボルがないアセンブリの場合のアセンブラ
  2. シンボルがあるアセンブリの場合のアセンブラ

1の場合は一度アセンブリを舐めるだけで良い。というのも、上から順に変換することが可能であるため。

2の場合、ファイルを舐める作業は2回必要である。というのも、シンボルは先に利用して後から宣言できるため。

こんな場合👇

@LOOP_END
0;JMP
...
...
...
(LOOP_END)

なので、1度目は、L命令を全てシンボルテーブルに保存して、2度目はそれを元に、出力していく。

特にこだわりがないので言語はkotlinでやってみた。

assemblerディレクトリにkotlinのプロジェクトファイルを保存してあります

Parserモジュール

import java.io.File
import java.io.InputStream

class Parser(filePath: String) {

    enum class COMMAND_TYPE {
        A_COMMAND,
        C_COMMAND,
        L_COMMAND,
        NONE
    }

    var inputLines = mutableListOf<String>()

    var commandIndex: Int = -1

    var binaryIndex: Int = -1

    init {
        val inputStream: InputStream = File(filePath).inputStream()
        val lineList = mutableListOf<String>()

        inputStream.bufferedReader().useLines { lines -> lines.forEach { lineList.add(it) } }
        lineList.forEach {
            // コメントは除去
            val formattedLine = it.split("//")[0]

            if (formattedLine.isEmpty()) {
                return@forEach
            }
            this.inputLines.add(formattedLine)
        }
    }

    // 入力にまだコマンドが存在するか
    fun hasMoreCommands(): Boolean {

        val nextLine = inputLines.getOrNull(this.commandIndex + 1)
        return nextLine !== null
    }

    // 次のコマンドを読み込む
    fun advance() {
        this.commandIndex++
    }

    // 現コマンドの種類を表す
    fun commandType(): COMMAND_TYPE {
        val command = getCurrentCommand()
        return when {
            command.startsWith("@") -> COMMAND_TYPE.A_COMMAND
            command.startsWith("(") && command.endsWith(")") -> COMMAND_TYPE.L_COMMAND
            else -> COMMAND_TYPE.C_COMMAND
        }
    }

    fun symbol(): String {
        val command = this.getCurrentCommand()
        val commandType = this.commandType()
        if (commandType !== COMMAND_TYPE.L_COMMAND && commandType !== COMMAND_TYPE.A_COMMAND) {
            return ""
        }

        if (commandType === COMMAND_TYPE.L_COMMAND) {
            return command.replace("(", "").replace(")", "")
        }

        return command.replace("@", "").trim()
    }

    fun dest(): String {
        val command = this.getCurrentCommand()
        if (!command.contains("=")) {
            return ""
        }
        return command.split("=")[0].trim()
    }

    fun comp(): String {
        val command = this.getCurrentCommand()
        var comp = command
        if (command.contains(";")) {
            comp = command.split(";")[0].trim()
        }

        if (command.contains("=")) {
            comp = command.split("=")[1].trim()
        }

        return comp
    }

    fun jump(): String {
        val command = this.getCurrentCommand()
        if (!command.contains(";")) {
            return ""
        }
        return command.split(";")[1].trim()
    }

    private final fun getCurrentCommand(): String {
        return inputLines.get(this.commandIndex).trim()
    }

    fun reset() {
        commandIndex = -1
        binaryIndex = -1
    }
}

愚直に実装。

ただし以下の部分に注意

// コメントは除去
val formattedLine = it.split("//")[0]

Codeモジュール

destはロジックに落とし込めた。jmpはロジック無理と割り切ってmapで行った。compはロジックに落とそうとすると非常に難しかった。

もしかしてと思って他の人の回答を見ると、そのまさかMapで持たせてた。まあそれが早いよね。

class Code() {
    companion object {
        fun dest(nimonic: String): String {
            var d = ""

            d += if (nimonic.contains("A")) {
                "1"
            } else {
                "0"
            }

            d += if (nimonic.contains("D")) {
                "1"
            } else {
                "0"
            }

            d += if (nimonic.contains("M")) {
                "1"
            } else {
                "0"
            }

            return d
        }

        fun comp(nimonic: String): String {
            val map = mapOf(
                    "0" to "0101010",
                    "1" to "0111111",
                    "-1" to "0111010",
                    "D" to "0001100",
                    "A" to "0110000",
                    "!D" to "0001101",
                    "!A" to "0110001",
                    "-D" to "0001111",
                    "-A" to "0110011",
                    "D+1" to "0011111",
                    "A+1" to "0110111",
                    "D-1" to "0001110",
                    "A-1" to "0110010",
                    "D+A" to "0000010",
                    "D-A" to "0010011",
                    "A-D" to "0000111",
                    "D&A" to "0000000",
                    "D|A" to "0010101",
                    "M" to "1110000",
                    "!M" to "1110001",
                    "-M" to "1110011",
                    "M+1" to "1110111",
                    "M-1" to "1110010",
                    "D+M" to "1000010",
                    "D-M" to "1010011",
                    "M-D" to "1000111",
                    "D&M" to "1000000",
                    "D|M" to "1010101"
            )

            return map.get(nimonic)!!
        }

        fun jump(nimonic: String): String {
            return when (nimonic) {
                "JGT" -> "001"
                "JEQ" -> "010"
                "JGE" -> "011"
                "JLT" -> "100"
                "JNE" -> "101"
                "JLE" -> "110"
                "JMP" -> "111"
                else -> "000"
            }
        }

    }
}

Main.kt

import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter


fun main(args: Array<String>) {
    // 入力
    val parser = Parser("/nand2tetris/projects/06/rect/Rect.asm")

    // 出力
    val fil = FileWriter("/nand2tetris/projects/06/rect/Rect.hack")

    // シンボルテーブル
    val symbolTable = SymbolTable()

    // 1. 変数保存
    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() == Parser.COMMAND_TYPE.L_COMMAND) {
            // 次の行のbinaryIndexを保存
            symbolTable.addEntry(parser.symbol(), parser.binaryIndex + 1)
        } else {
            // L命令ではないので、機械語として出力される=>機械語の行数をカウントアップ
            parser.binaryIndex++
        }
    }

    parser.reset()

    // 2. 変換出力
    val pw = PrintWriter(BufferedWriter(fil))

    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() === Parser.COMMAND_TYPE.NONE) {
            continue
        }

        if (parser.commandType() === Parser.COMMAND_TYPE.L_COMMAND) {
            continue
        }

        if (parser.commandType() === Parser.COMMAND_TYPE.A_COMMAND) {

            val symbol = parser.symbol()

            // シンボルに文字が含まれる場合
            val address = if (symbol.contains(Regex("""\D+"""))) {

                // シンボルテーブルに含まれていない場合
                if (!symbolTable.contains(symbol)) {
                    // 新たに追加
                    symbolTable.addEntry(symbol)
                }

                // シンボルテーブルからアドレスを解決
                symbolTable.getAddress(symbol)
            } else {
                // シンボルが数値の場合
                Integer.parseInt(symbol)
            }

            pw.println("%016d".format(Integer.toBinaryString(address).toLong()))
            continue
        }

        // C命令
        val binaryString = "111" + Code.comp(parser.comp()) + Code.dest(parser.dest()) + Code.jump(parser.jump())
        pw.println(binaryString)
    }

    pw.close()
}


エントリーポイント。ファイルを二回なめている。

commandIndexで、アセンブリ言語の行数をカウントアップしているが、機械語の行数とは一致しないので注意。途中で気づいて、 binaryIndexを生やしてカバーリング。👇

    // 1. 変数保存
    while (parser.hasMoreCommands()) {
        parser.advance()
        if (parser.commandType() == Parser.COMMAND_TYPE.L_COMMAND) {
            // 次の行のbinaryIndexを保存
            symbolTable.addEntry(parser.symbol(), parser.binaryIndex + 1)
        } else {
            // L命令ではないので、機械語として出力される=>機械語の行数をカウントアップ
            parser.binaryIndex++
        }
    }

SymbolTableモジュール

class SymbolTable() {
    var table = mutableMapOf<String, Int>()

    init {
        table.putAll(mapOf(
                "R0" to 0,
                "R1" to 1,
                "R2" to 2,
                "R3" to 3,
                "R4" to 4,
                "R5" to 5,
                "R6" to 6,
                "R7" to 7,
                "R8" to 8,
                "R9" to 9,
                "R10" to 10,
                "R11" to 11,
                "R12" to 12,
                "R13" to 13,
                "R14" to 14,
                "R15" to 15,
                "SP" to 0,
                "LCL" to 1,
                "ARG" to 2,
                "THIS" to 3,
                "THAT" to 4,
                "SCREEN" to 16384,
                "KBD" to 24576
        ))
    }

    // 保存開始アドレス
    var index = 16

    fun addEntry(symbol: String, address: Int) {
        table[symbol] = address
    }

    fun addEntry(symbol: String) {
        table[symbol] = this.index++
    }

    fun contains(symbol: String): Boolean {
        return table.contains(symbol)
    }

    fun getAddress(symbol: String): Int {
        return table.get(symbol)!!
    }
}

愚直に実装。特に面白い点なし。

Pong.hack

30000行近くのアセンブリ機械語に変換して実行。何気に面白い。

f:id:Kouchannel55:20190123103231p:plain
Pong.hackの起動画面。右上がゲーム画面

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装5章)

こんにちは。ようやくコンピュータの核の部分を実装する章までやってきた。。

地味に長かった。がなんとか終わりそう

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3

vol.4

vol.5 いまここ

5章 コンピュータアーキテクチャ

理論(一般的なコンピュータ)

ノイマンアーキテクチャ

CPUが中央にあり、メモリ・入力デバイス・出力デバイス等と通信する。

メモリ

データメモリ・命令メモリの2種類に分けられる。アーキテクチャによっては別のメモリユニットになり得る。

どちらにせよバイナリで保存される

データメモリ

プログラムで利用するデータを保存することができる

3章で学んだメモリのように、読み込み・書き込みができる

命令メモリ

読み込みのみである

命令メモリとCPUは、以下のようなやりとりを行う

  1. 命令メモリを一つフェッチ
  2. CPUが命令をデコード・実行・次のアドレス計算
  3. 1に戻る(次のアドレスに対して)

CPU

CPUは、ALUとレジスタと制御ユニットで構成されている

ALU

3章で作ったもの。汎用的な演算装置

レジスタ

データメモリよりも高速にデータの保存・取得ができる領域

ただしデータメモリに比べはるかにサイズが小さい

データレジスタ=はやくて便利なメモリ?

アドレスレジスタ=読み書きに利用するためのアドレスを保存するレジスタ

プログラムカウンタレジスタ=現在実行している命令メモリのアドレスを保存するレジスタ

制御ユニット

CPUが命令をデコードするために必要なもの。

命令メモリを一つフェッチCPUが命令をデコード・実行・次のアドレス計算

ここまでを踏まえて、CPUの処理は、以下のように分割できる

  1. 命令メモリを一つフェッチ
  2. 制御ユニットが命令をデコード
  3. 命令を実行
  4. 次の命令メモリアドレスを計算

入力出力デバイス

全ての入出力デバイスはメモリマップドI/Oという仕様に従っている

キーボード=ベースアドレスが決められており、キーボードは常に入力値をベースアドレスに格納している。したがってCPUはベースアドレスを取得するだけでよい

スクリーン=ベースアドレスから描画領域分のアドレスの領域のメモリの値が画面に描画されると決められており、CPUはその領域のメモリの値を変更するだけで良い

理論(Hackハードウェア)

A命令

前章で紹介したが、

0xxxxxxxxxxxxxxxというフォーマットに沿う。

xxxxxxxxxxxxxxxの値がアドレスレジスタに保存される

C命令

前章で紹介したが、

111accccccdddjjjというフォーマットに沿う。

このフォーマットはALUのフォーマットと非常に近い。それに気づけるかどうか少し難しいところ。

Hackハードウェアの構成

  • 命令メモリ
  • CPU
  • データメモリ
    • RAM
    • メモリマップ(スクリーン)
    • メモリマップ(キーボード)

実装

データメモリ

データメモリは、RAMとスクリーンとキーボードで構成されている。

キモは、入力アドレスaddress[15]の値が、RAMかスクリーン(16384~24575)か24576(キーボード)かの判定部分。3パターンなのでDMux4Wayが使える

    // アドレス13,14で判断
    // address[14]=1 かつ address[13]=1 はキーボード
    // address[14]=1 かつ address[13]=0 はスクリーン
    // それ以外はRAM
    DMux4Way(in=load, sel[0]=address[13], sel[1]=address[14], a=isload-ram1, b=isload-ram2, c=isload-screen, d=isload-keyboard);

👇以下全文

CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:

    // アドレス13,14で判断
    // address[14]=1 かつ address[13]=1 はキーボード
    // address[14]=1 かつ address[13]=0 はスクリーン
    // それ以外はRAM
    DMux4Way(in=load, sel[0]=address[13], sel[1]=address[14], a=isload-ram1, b=isload-ram2, c=isload-screen, d=isload-keyboard);

    Or(a=isload-ram1, b=isload-ram2, out=isload-ram);

    // RAM
    RAM16K(in=in, load=isload-ram, address=address[0..13], out=out-ram);

    // スクリーン
    Screen(in=in, load=isload-screen, address=address[0..12], out=out-screen);

    // キーボード
    Keyboard(out=out-keyboard);

    Mux4Way16(a=out-ram, b=out-ram, c=out-screen, d=out-keyboard, sel[0]=address[13], sel[1]=address[14], out=out);
}

CPU

最初はスイスイ行ったけどところどころハマった。👇以下全文

CHIP CPU {

    IN  inM[16],         // M value input  (M = contents of RAM[A])
        instruction[16], // Instruction for execution
        reset;           // Signals whether to re-start the current
                         // program (reset==1) or continue executing
                         // the current program (reset==0).

    OUT outM[16],        // M value output
        writeM,          // Write to M? 
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    PARTS:

    // CPU制御ユニット
    // a命令かc命令か
    // a-instruction
    // c-instruction
    Or(a=instruction[15], b=instruction[15], out=c-instruction);
    Not(in=c-instruction, out=a-instruction);

    // Dレジスタの保存はC命令の場合のみ
    And(a=instruction[4], b=c-instruction, out=isload-d);

    // instruction[4]はd2=>Dに保存するかどうか
    DRegister(in=alu-out, load=isload-d, out=out-d);

    // Aレジスタが保存するのは、A命令時の定数かC命令時の計算結果
    Mux16(a=alu-out, b=instruction, sel=a-instruction, out=in-a);

    // A命令でもC命令でも、結局のところAレジスタをloadするかどうか
    Or(a=instruction[5], b=a-instruction, out=isload-a);

    // instruction[5]はd1=>Aに保存するかどうか
    // outを切り取るのは問題ない??
    ARegister(in=in-a, load=isload-a, out[0..15]=out-a, out[0..14]=addressM);

    // instruction[12]はa。ALUのyはa=1ならM,a=0ならA
    // ALUにMを使うかAを使うか
    Mux16(a=out-a, b=inM, sel=instruction[12], out=aluin-y);

    // メモリの演算
    // 命令がそのまま流れる?
    ALU(x=out-d, y=aluin-y, zx=instruction[11], nx=instruction[10], zy=instruction[9], ny=instruction[8], f=instruction[7], no=instruction[6], out=alu-out, out[0..7]=alu-out1, out[8..15]=alu-out2, out[15]=alu-minus, out=outM, zr=alu-zr, ng=alu-ng);


    // Mに書き込み行うかどうか
    // CPUの領域外?
    // C命令の時のみ出力
    And(a=instruction[3], b=c-instruction, out=writeM);
    
    // jmp判定
    // 負の数はa-out[15]=1
    // 正の数はa-out[15]=0
    // 以下の変数を計算
    // alu-minus
    // alu-plus
    // alu-iszero
    Not(in=alu-minus, out=alu-plusorzero); //正の数または0
    Or8Way(in=alu-out1, out=temp1); // 0判定その1
    Or8Way(in=alu-out2, out=temp2); // 0判定その2
    Or(a=temp1, b=temp2, out=temp3); // 0判定その3
    Not(in=temp3, out=alu-iszero);
    And(a=alu-plusorzero, b=temp3, out=alu-plus);

    // カウンタ
    // C命令の場合のみ飛ぶのを忘れずに
    // j1, j2, j3の計算
    And(a=alu-minus, b=instruction[2], out=is-j1);
    And(a=alu-iszero, b=instruction[1], out=is-j2);
    And(a=alu-plus, b=instruction[0], out=is-j3);
    // 条件に合致するか
    Or8Way(in[0]=is-j1, in[1]=is-j2, in[2]=is-j3, out=is-jump);
    // なおかつ、C命令か
    And(a=is-jump, b=c-instruction, out=can-jump);
    // PC処理
    // incは基本true
    PC(in=out-a, load=can-jump, inc=true, reset=reset, out[0..14]=pc);
}

ハマったこと

j1, j2, j3の判定

alu-out(aluの出力結果)が[15]=trueの場合、alu-outが負の数と判断できる

=>Notをとれば、正の数かどうか判断できる

==> Not(in=alu-out[15], out=alu-plus)

これは正しくなく、alut-out[15]のNotを取ると、正の数か0かになります。なので、

Not(in=alu-out[15], out=alu-plusorzero)として

And(a=alu-plusorzero, b=temp3, out=alu-plus)で正の数と判定しないといけないので注意

DRegister/ARegisterのload

DRegisterloadは、 instruction[4]を見がちですが、A命令の場合も考慮して、以下のようにしましょう

    // Dレジスタの保存はC命令の場合のみ
    And(a=instruction[4], b=c-instruction, out=isload-d);

    // instruction[4]はd2=>Dに保存するかどうか
    DRegister(in=alu-out, load=isload-d, out=out-d);

ARegisterもだいたい同じ。isloadの条件が少し違うので注意

    // A命令でもC命令でも、結局のところAレジスタをloadするかどうか
    Or(a=instruction[5], b=a-instruction, out=isload-a);

    // instruction[5]はd1=>Aに保存するかどうか
    // outを切り取るのは問題ない??
    ARegister(in=in-a, load=isload-a, out[0..15]=out-a, out[0..14]=addressM);

Sub bus of an internal node may not be used

ALU(... , out=alu-out, out=outM, zr=alu-zr, ng=alu-ng);として

Or8Way(in=alu-out[0..7], out=temp1); // 0判定その1
Or8Way(in=alu-out[8..15], out=temp2); // 0判定その2
Or(a=temp1, b=temp2, out=temp3); // 0判定その3

とすると変数が使われてないかもよと怒られる。変数ちゃんと使ってるのにと思ったら同様のエラーの質問発見。

http://nand2tetris-questions-and-answers-forum.32033.n3.nabble.com/Sub-bus-of-an-internal-node-may-not-be-used-td4031198.html

正しくは、ALU(... , out=alu-out1, out[0..7]=alu-out1, out[8..15]=alu-out2, out=outM, zr=alu-zr, ng=alu-ng);として

    Or8Way(in=alu-out1, out=temp1); // 0判定その1
    Or8Way(in=alu-out2, out=temp2); // 0判定その2
    Or(a=temp1, b=temp2, out=temp3); // 0判定その3

としないといけない

PCのincはtrue?

ハマったわけではないけど、PCのincは常にtrueにしてるがこれでいいものか。。。

   // PC処理
    // incは基本true
    PC(in=out-a, load=can-jump, inc=true, reset=reset, out[0..14]=pc);

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装4章)

こんにちは。敗北を知った4章です

アセンブリのとこまでやってきたけど心が折れそう

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3

vol.4 いまここ

4章 機械語

理論

アドレッシングモード

要求されたメモリのワードに対してそのアドレスを指定する方法

直接アドレッシング

引数にアドレスを直接渡してアクセスすること

LOAD R1, 67
LOAD R1, bar

イミディエイトアドレッシング

引数の値を直接渡す

LOADI R1, 67

関節アドレッシング

Hack機械語の仕様

メモリアドレス空間

  • 命令メモリ

CPUは命令メモリを読み込み実行する。リードオンリー

  • データメモリ

レジスタ

D/Aの2種類。

D=データ値を保存する。

A=データ値とアドレスレジスタと解釈される=値として利用したりアドレスとして利用したりできる

M=Aのアドレスが参照している値(仮想の値。内部でAレジスタが示すアドレスの値を取得するルール

命令

命令は、16bitである。そのうち、先頭の1bitはA/Cの選別に利用する

A命令

Aレジスタに15bitの値を設定する命令

@100 // 100をAレジスタに保存

@500 // 500をAレジスタに保存

@R0  // 先頭のレジスタをAレジスタに保存

C命令

dest = comp;jampで構成された命令。

@sum   //A命令
M=D    //C命令

@index //A命令
D=M    //C命令
D;JEQ  //C命令

シンボル

  • R0~R15は、 アドレス0~16のレジスタとして使用可能
  • @SCREENは、スクリーンの左上のアドレスを示す
  • @KBDは、キーボードの出力アドレスを示す。値は未入力なら0,入力しているなら入力値である

変数シンボル

ユーザーが定義できるシンボル。アドレスは16から始まる

@sum  // A=16である
M=1   // アドレス161が設定されている

実装

まずは掛け算 を実装。

加算減算しかできないので、愚直に実装

@sum
M=0

// 残りカウントをR1で初期化
@R1
D=M
@count 
M=D

(LOOP)
    // チェック
    @count
    D=M
    @END
    D;JEQ

    // 合計値計算
    @R0
    D=M
    @sum
    M=M+D

    // カウントを減らす
    @count
    M=M-1

    // ループ繰り返す
    @LOOP
    0;JMP
(END)

// R2に記録
@sum
D=M
@R2
M=D

@END
0;JMP

次に、スクリーンの入力に合わせて画面を白黒切り替える実装。くそほど詰まった :innocent:

// 初期化
// 最大スクリーンアドレスの計算
@8192 //256*32
D=A
@SCREEN
D=D+A
@MAXADDRESS //最大スクリーンアドレス
M=D

// 以下、無限ループ
(KEY)

@SCREEN
D=A
@address // スクリーンアドレス初期化
M=D

@KBD //キーボード取得
D=M

// 振り分け
@WHITE
D;JEQ
@BLACK
0;JMP

(WHITE)
@color
M=0
@LOOP
0;JMP

(BLACK)
@color
M=-1
@LOOP
0;JMP

// スクリーン書き換え
(LOOP)
@color
D=M

@address
A=M // 値のアドレスに移動
M=D //アドレスの値をcolorに

D=A+1 //アドレスに1加えたところに移動
@address // 必要!!!
M=D // 次に移動するために新たなアドレスを値として保存

@MAXADDRESS
D=M-D //Dが0かどうか

@LOOP
D;JNE

@KEY
0;JMP
(END)

👇この部分で2時間ほどつまった。

@addressには現在のアドレスを入れているが、A=A+1とすると同時に@addressも一つずれると思い込んでいた(実際は、@addressは元のアドレスのまま。動かない。値が動くだけ)

@address
A=M // 値のアドレスに移動
M=D //アドレスの値をcolorに

D=A+1 //アドレスに1加えたところに移動
@address // 必要!!!
M=D // 次に移動するために新たなアドレスを値として保存

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装3章)

こんにちは。3章まできました。

www.oreilly.co.jp

記録用git

vol.1

vol.2

vol.3 いまここ

3章 順序回路

理論

組み合わせ回路

時間に依存しない回路。論理演算や算術演算。

順序回路

時間に依存する回路。一つ以上のDFFが組み込まれている回路。組み合わせ回路が含まれていることもある。

クロック

継続的にマスタクロックが送信する信号。プラットフォームの全ての順序回路に対して送信される。周期。

DFF

D型フリップフロップ

順序回路の基本的要素。

out(t)=in(t-1)

簡単に言えば、クロックが進むまで待機する回路。

1ビットレジスタ

記憶装置。DFFを拡張して情報を格納できる。仕組みはこんな感じ。

f:id:Kouchannel55:20190113152623p:plain

load(ロードビット)という値がキモ。周期の前と後で考える。後を今回、前を前回ととりあえず呼ぶ。

👇load=falseの場合は、前回の自身の出力をそのまま今回の出力とする。この仕組みは、値をレジスタが記憶していると言っても良い。

f:id:Kouchannel55:20190113152629p:plain

👇load=trueの場合は、今回の入力をそのまま今回の出力とする。この仕組みは、値を書き換えると言ってもいい。

f:id:Kouchannel55:20190113152619p:plain

wビットレジスタ

通常のコンピュータであれば、1ビットレジスタでは構成されておらず、並列に処理できるwビットレジスタで構成されている。

👇loadはあくまで1ビットであることに注意

f:id:Kouchannel55:20190113152605p:plain

メモリ

レジスタの集合体。

並列にwビットレジスタが配置されている。

周期ごとに、

  • どのレジスタに対して(address
  • 読み込みか書き込みか(load
  • 入力値(in

を入力として与える。

カウンタ

他の順序回路とは少し違うみたい。

この書籍では、

  • カウンタを0にするかどうか(reset。1の場合、出力を0にする)
  • 読み込みか書き込みか(load。1の場合、前回の出力を今回の出力にする)
  • 加算するか(inc。1の場合、前回の出力に1=00....000001を加算したものを出力にする)

を入力値としている

実装

カウンタ(PC)が難しかった。

2章でじっそうしたものがつかえる

と書いてあったので、ALUが使えるかと思ったが無理だった。

以下、PCの実装。

実装後に他人の実装見てたけど、4~7行ほどが多かった。

ベストアンサー説💯

/**
 * A 16-bit counter with load and reset control bits.
 * if      (reset[t] == 1) out[t+1] = 0
 * else if (load[t] == 1)  out[t+1] = in[t]
 * else if (inc[t] == 1)   out[t+1] = out[t] + 1  (integer addition)
 * else                    out[t+1] = out[t]
 */

CHIP PC {
    IN in[16],load,inc,reset;
    OUT out[16];

    PARTS:

    //2
    // inc
    Add16(a=dffout, b[0]=inc, out=temp-out1);
    Mux4Way16(a=temp-out1, b=false, c=in, d=false, sel[0]=reset, sel[1]=load, out=temp-out2);
    Register(in=temp-out2, load=true, out=out, out=dffout);
}

低レイヤチョットワカル(nand2tetris/コンピュータシステムの理論と実装2章)

こんにちは。

つづけて2章いきましょう。

www.oreilly.co.jp

記録用git

vol.1

vol.2 いまここ

2章 ブール算術

オーバーフロー

nビット同士の加算によって、繰り上がりビットが発生すること。発生した繰り上がりビットをオーバーフロービットとも呼ぶ

符号付2進数

0~7を表す2進数は、3ビットで事足りる 111=7

これにマイナスを考慮する場合は、先頭に1ビットを付け加えて、そのビットが0=>正。1=>負とする

例えば、

  • 0111=7
  • 0000=0
  • 1111=-1
  • 1001=-7

正負変換

全反転させて-1

0111=>1000=>1001

半加算器

ふたつのビットの和を求める

全加算器

みっつのビットの和を求める

加算器

2つのnビットの和を求める

ALU

基本的な算術ができる汎用的な演算器。コンピュータによって実装が異なる。

掛け算・割り算などはここで実装しないが、OS側で実装する。

OSとALUがそれぞれどこの算術までカバーするかがキモ

出力は複数指定できる

これは知らなかった。こちらの記事で気づきました。ありがとうございます :bow:

http://blog.tojiru.net/article/426464326.html

実装

ALUくそ難しかった。

zrとngが厄介。

zr

計算結果が0の場合に1を返す。

out=0000000000000000の場合は、

  1. 0000000000000000に分け(out1, out2)
  2. 前半後半でそれぞれorで全て01が一つでも存在しているかチェック(temp1-zr, temp2-zr)
  3. 前半後半それぞれの結果をor(notzr)
  4. notzr=1なら0ではない、Notで反転(zr)
   Or8Way(in=out1, out=temp1-zr);
    Or8Way(in=out2, out=temp2-zr);
    Or(a=temp1-zr, b=temp2-zr, out=notzr);
    Not(in=notzr, out=zr);

ng

符号付2進数で紹介したが、先頭のビットが1なら負の数。よって、合計値を計算したさいの、out[15]=1なら負の数すなはちng=1

Mux16(a=temp-result, b=temp-noresult, sel=no, out=out, out[0..7]=out1, out[8..15]=out2, out[15]=ng);

僕の回答

// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/02/ALU.hdl

/**
 * The ALU (Arithmetic Logic Unit).
 * Computes one of the following functions:
 * x+y, x-y, y-x, 0, 1, -1, x, y, -x, -y, !x, !y,
 * x+1, y+1, x-1, y-1, x&y, x|y on two 16-bit inputs, 
 * according to 6 input bits denoted zx,nx,zy,ny,f,no.
 * In addition, the ALU computes two 1-bit outputs:
 * if the ALU output == 0, zr is set to 1; otherwise zr is set to 0;
 * if the ALU output < 0, ng is set to 1; otherwise ng is set to 0.
 */

// Implementation: the ALU logic manipulates the x and y inputs
// and operates on the resulting values, as follows:
// if (zx == 1) set x = 0        // 16-bit constant
// if (nx == 1) set x = !x       // bitwise not
// if (zy == 1) set y = 0        // 16-bit constant
// if (ny == 1) set y = !y       // bitwise not
// if (f == 1)  set out = x + y  // integer 2's complement addition
// if (f == 0)  set out = x & y  // bitwise and
// if (no == 1) set out = !out   // bitwise not
// if (out == 0) set zr = 1
// if (out < 0) set ng = 1

CHIP ALU {
    IN  
        x[16], y[16],  // 16-bit inputs        
        zx, // zero the x input?
        nx, // negate the x input?
        zy, // zero the y input?
        ny, // negate the y input?
        f,  // compute out = x + y (if 1) or x & y (if 0)
        no; // negate the out output?

    OUT 
        out[16], // 16-bit output
        zr, // 1 if (out == 0), 0 otherwise
        ng; // 1 if (out < 0),  0 otherwise

    PARTS:
    Mux16(a=x, sel=zx, out=tempx0);
    Mux16(a=y, sel=zy, out=tempy0);
 
    Not16(in=tempx0, out=notx);
    Not16(in=tempy0, out=noty);
  
    Mux16(a=tempx0, b=notx, sel=nx, out=tempx1);
    Mux16(a=tempy0, b=noty, sel=ny, out=tempy1);

    Add16(a=tempx1, b=tempy1, out=add16);
    And16(a=tempx1, b=tempy1, out=and16);

    Mux16(a=and16, b=add16, sel=f, out=temp-result);

    Not16(in=temp-result, out=temp-noresult);

    Mux16(a=temp-result, b=temp-noresult, sel=no, out=out, out[0..7]=out1, out[8..15]=out2, out[15]=ng);

    Or8Way(in=out1, out=temp1-zr);
    Or8Way(in=out2, out=temp2-zr);
    Or(a=temp1-zr, b=temp2-zr, out=notzr);
    Not(in=notzr, out=zr);
}