n-channel

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 3c1904252a6c0afedb50ce154067f734b0087867
Author: Samdal <samdal@protonmail.com>
Date:   Thu, 20 Feb 2025 19:19:11 +0100

initial commit

Diffstat:
A.gitignore | 5+++++
A404.html | 11+++++++++++
AGemfile | 37+++++++++++++++++++++++++++++++++++++
AGemfile.lock | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A_config.yml | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
A_data/navigation.yml | 9+++++++++
A_includes/anchor_headings.html | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A_includes/head.html | 36++++++++++++++++++++++++++++++++++++
A_includes/inline.scss | 2++
A_includes/navigation.html | 10++++++++++
A_layouts/compress.html | 10++++++++++
A_layouts/default.html | 28++++++++++++++++++++++++++++
A_layouts/page.html | 11+++++++++++
A_layouts/post.html | 12++++++++++++
A_layouts/search.html | 27+++++++++++++++++++++++++++
A_layouts/tag.html | 17+++++++++++++++++
A_layouts/tags.html | 16++++++++++++++++
A_pages/about.md | 24++++++++++++++++++++++++
A_pages/search.md | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
A_plugins/tags.rb | 27+++++++++++++++++++++++++++
A_posts/2024-01-20-Simplifying-state.md | 360+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A_posts/2024-05-08-VGA-Kontroller.md | 690+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A_posts/2025-02-20-hello-world (copy 1).md | 17+++++++++++++++++
A_posts/2025-02-20-hello-world (copy 2).md | 17+++++++++++++++++
A_posts/2025-02-20-hello-world (copy 3).md | 17+++++++++++++++++
A_posts/2025-02-20-hello-world (copy 4).md | 17+++++++++++++++++
A_posts/2025-02-20-hello-world.md | 17+++++++++++++++++
A_sass/_main.scss | 233+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aassets/images/VGA/VGA_fig1.jpeg | 0
Aassets/images/VGA/VGA_fig10.jpeg | 0
Aassets/images/VGA/VGA_fig11.jpeg | 0
Aassets/images/VGA/VGA_fig12.jpeg | 0
Aassets/images/VGA/VGA_fig13.jpeg | 0
Aassets/images/VGA/VGA_fig14.jpeg | 0
Aassets/images/VGA/VGA_fig15.jpeg | 0
Aassets/images/VGA/VGA_fig16.jpeg | 0
Aassets/images/VGA/VGA_fig17.jpeg | 0
Aassets/images/VGA/VGA_fig18.jpeg | 0
Aassets/images/VGA/VGA_fig2.jpeg | 0
Aassets/images/VGA/VGA_fig3.jpeg | 0
Aassets/images/VGA/VGA_fig4.jpeg | 0
Aassets/images/VGA/VGA_fig5.jpeg | 0
Aassets/images/VGA/VGA_fig6.jpeg | 0
Aassets/images/VGA/VGA_fig7.jpeg | 0
Aassets/images/VGA/VGA_fig8.jpeg | 0
Aassets/images/VGA/VGA_fig9.jpeg | 0
Aassets/images/c-programming-language-chito.png | 0
Aassets/js/search-script.min.js | 6++++++
Aatom.xml | 28++++++++++++++++++++++++++++
Afeed.json | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aindex.html | 15+++++++++++++++
Apackage-lock.json | 27+++++++++++++++++++++++++++
Apackage.json | 5+++++
Asearch.js | 6++++++
Asearch.json | 17+++++++++++++++++
55 files changed, 2205 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,5 @@ +_site +.sass-cache +.jekyll-cache +.jekyll-metadata +vendor diff --git a/404.html b/404.html @@ -0,0 +1,11 @@ +--- +layout: page +title: "404: Page not found" +permalink: 404.html +--- + + + +Sorry, we've misplaced that URL or it's pointing to something that doesn't exist. + + diff --git a/Gemfile b/Gemfile @@ -0,0 +1,37 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.4.1" +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.5" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] + +gem 'jekyll-paginate' +gem 'jekyll-seo-tag' +gem 'jekyll-sitemap' diff --git a/Gemfile.lock b/Gemfile.lock @@ -0,0 +1,177 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + bigdecimal (3.1.9) + colorator (1.1.0) + concurrent-ruby (1.3.5) + csv (3.3.2) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.1) + ffi (1.17.1-aarch64-linux-gnu) + ffi (1.17.1-aarch64-linux-musl) + ffi (1.17.1-arm-linux-gnu) + ffi (1.17.1-arm-linux-musl) + ffi (1.17.1-arm64-darwin) + ffi (1.17.1-x86-linux-gnu) + ffi (1.17.1-x86-linux-musl) + ffi (1.17.1-x86_64-darwin) + ffi (1.17.1-x86_64-linux-gnu) + ffi (1.17.1-x86_64-linux-musl) + forwardable-extended (2.6.0) + google-protobuf (4.29.3) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-aarch64-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86-linux) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.29.3-x86_64-linux) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jekyll (4.4.1) + addressable (~> 2.4) + base64 (~> 0.2) + colorator (~> 1.0) + csv (~> 3.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + json (~> 2.6) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (~> 0.3, >= 0.3.6) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-paginate (1.1.0) + jekyll-sass-converter (3.1.0) + sass-embedded (~> 1.75) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + json (2.10.1) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + minima (2.5.2) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.1) + rouge (4.5.1) + safe_yaml (1.0.5) + sass-embedded (1.85.0) + google-protobuf (~> 4.29) + rake (>= 13) + sass-embedded (1.85.0-aarch64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-aarch64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-aarch64-linux-musl) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-arm-linux-androideabi) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-arm-linux-gnueabihf) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-arm-linux-musleabihf) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-arm64-darwin) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-riscv64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-riscv64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-riscv64-linux-musl) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-x86_64-cygwin) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-x86_64-darwin) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-x86_64-linux-android) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-x86_64-linux-gnu) + google-protobuf (~> 4.29) + sass-embedded (1.85.0-x86_64-linux-musl) + google-protobuf (~> 4.29) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.9.1) + +PLATFORMS + aarch64-linux + aarch64-linux-android + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-androideabi + arm-linux-gnu + arm-linux-gnueabihf + arm-linux-musl + arm-linux-musleabihf + arm64-darwin + riscv64-linux-android + riscv64-linux-gnu + riscv64-linux-musl + ruby + x86-linux + x86-linux-gnu + x86-linux-musl + x86_64-cygwin + x86_64-darwin + x86_64-linux-android + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + http_parser.rb (~> 0.6.0) + jekyll (~> 4.4.1) + jekyll-feed (~> 0.12) + jekyll-paginate + jekyll-seo-tag + jekyll-sitemap + minima (~> 2.5) + tzinfo (>= 1, < 3) + tzinfo-data + wdm (~> 0.1) + +BUNDLED WITH + 2.6.4 diff --git a/_config.yml b/_config.yml @@ -0,0 +1,52 @@ +site: n-channel +url: #yoururl +title: n-channel +description: Personal blog about electronics and programming +permalink: pretty + +baseurl: "" + +highlighter: none + +exclude: + - .jekyll-cache + +compress_html: + clippings: all + comments: ["<!-- ", " -->"] + +sass: + sass_dir: _sass + style: :compressed + +plugins: + - jekyll-paginate + - jekyll-seo-tag + - jekyll-sitemap + +collections: + pages: + output: true + permalink: /:name + posts: + output: true + permalink: /:year/:month/:day/:slug + +defaults: + - + scope: + path: "" + values: + layout: "default" + - + scope: + path: "" + type: "pages" + values: + layout: "page" + - + scope: + path: "" + type: "posts" + values: + layout: "post" diff --git a/_data/navigation.yml b/_data/navigation.yml @@ -0,0 +1,9 @@ +links: +- title: Blog + url: / +- title: About + url: /about +- title: Search + url: /search +- title: Rss + url: /atom.xml diff --git a/_includes/anchor_headings.html b/_includes/anchor_headings.html @@ -0,0 +1,105 @@ +{% capture headingsWorkspace %} + {% comment %} + Version 1.0.4 + https://github.com/allejo/jekyll-anchor-headings + + "Be the pull request you wish to see in the world." ~Ben Balter + + Usage: + {% include anchor_headings.html html=content %} + + Parameters: + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll + + Optional Parameters: + * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content + * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title` + * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available + * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space + * anchorTitle (string) : '' - The `title` attribute that will be used for anchors + * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored + * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored + * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content + * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content + + Output: + The original HTML with the addition of anchors inside of all of the h1-h6 headings. + {% endcomment %} + + {% assign minHeader = include.h_min | default: 1 %} + {% assign maxHeader = include.h_max | default: 6 %} + {% assign beforeHeading = include.beforeHeading %} + {% assign nodes = include.html | split: '<h' %} + + {% capture edited_headings %}{% endcapture %} + + {% for _node in nodes %} + {% capture node %}{{ _node | strip }}{% endcapture %} + + {% if node == "" %} + {% continue %} + {% endif %} + + {% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %} + {% assign headerLevel = nextChar | times: 1 %} + + <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's try to fix it --> + {% if headerLevel == 0 %} + {% if nextChar != '<' and nextChar != '' %} + {% capture node %}<h{{ node }}{% endcapture %} + {% endif %} + + {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %} + {% continue %} + {% endif %} + + {% assign _workspace = node | split: '</h' %} + {% assign _idWorkspace = _workspace[0] | split: 'id="' %} + {% assign _idWorkspace = _idWorkspace[1] | split: '"' %} + {% assign html_id = _idWorkspace[0] %} + + {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %} + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} + + <!-- Build the anchor to inject for our heading --> + {% capture anchor %}{% endcapture %} + + {% if html_id and headerLevel >= minHeader and headerLevel <= maxHeader %} + {% capture anchor %}href="#{{ html_id }}"{% endcapture %} + + {% if include.anchorClass %} + {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %} + {% endif %} + + {% if include.anchorTitle %} + {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', header }}"{% endcapture %} + {% endif %} + + {% if include.anchorAttrs %} + {% capture anchor %}{{ anchor }} {{ include.anchorAttrs }}{% endcapture %} + {% endif %} + + {% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', header | default: '' }}</a>{% endcapture %} + + <!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it --> + {% if beforeHeading %} + {% capture anchor %}{{ anchor }} {% endcapture %} + {% else %} + {% capture anchor %} {{ anchor }}{% endcapture %} + {% endif %} + {% endif %} + + {% capture new_heading %} + <h{{ _hAttrToStrip }} + {{ include.bodyPrefix }} + {% if beforeHeading %} + {{ anchor }}{{ header }} + {% else %} + {{ header }}{{ anchor }} + {% endif %} + {{ include.bodySuffix }} + </h{{ _workspace | last }} + {% endcapture %} + {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %} + {% endfor %} +{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }} +\ No newline at end of file diff --git a/_includes/head.html b/_includes/head.html @@ -0,0 +1,36 @@ +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + + {% seo title=false %} + + <title> + {% if page.tag or page.title %} + {% if page.tag %} + {{ page.tag | escape }} + {% else %} + {{ page.title | escape }} + {% endif %} + - + {% endif %} + {% if paginator and paginator.page and paginator.total_pages > 1 and paginator.page > 1 %} + Page {{ paginator.page }} of {{ paginator.total_pages }} - + {% endif %} + {{ site.title | escape }} + {% unless page.tag or page.title %} + - {{ site.description | escape }} + {% endunless %} + </title> + + <link rel="alternate" type="application/atom+xml" title="{{ site.title }}" href="{{ site.baseurl }}/atom.xml"> + <link rel="alternate" type="application/json" title="{{ site.title }}" href="{{ "/feed.json" | prepend: site.baseurl | prepend: site.url }}" /> + <link rel="sitemap" type="application/xml" title="sitemap" href="{{ site.baseurl }}/sitemap.xml" /> + + <style> + {% capture include_to_scssify %} + {% include inline.scss %} + {% endcapture %} + {{ include_to_scssify | scssify }} + </style> + +</head> diff --git a/_includes/inline.scss b/_includes/inline.scss @@ -0,0 +1 @@ +@import "main"; +\ No newline at end of file diff --git a/_includes/navigation.html b/_includes/navigation.html @@ -0,0 +1,10 @@ +<header aria-hidden="true" class="no-print"> + <a href="/"><img style="margin-bottom: 5px; max-width: 180px; max-height: 244px;" src="/assets/images/c-programming-language-chito.png"> </a> + <nav role="navigation" aria-hidden="true"> + <ul> + {% for item in site.data.navigation.links %} + <li><a href="{{ item.url }}" {% if item.url == page.url %} class="active"{% endif %}>{{ item.title }}</a></li> + {% endfor %} + </ul> + </nav> +</header> diff --git a/_layouts/compress.html b/_layouts/compress.html @@ -0,0 +1,10 @@ +--- +# Jekyll layout that compresses HTML +# v3.0.4 +# http://jch.penibelst.de/ +# © 2014–2015 Anatol Broder +# MIT License +--- + +{% capture _LINE_FEED %} +{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "<!-- -->" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains "</pre>" %}<pre{{ _pres.first }}</pre>{% endif %}{% unless _pre_before contains "</pre>" and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} <table id="compress_html_profile_{{ site.time | date: "%Y%m%d" }}" class="compress_html_profile"> <thead> <tr> <td>Step <td>Bytes <tbody> <tr> <td>raw <td>{{ content | size }}{% if _profile_endings %} <tr> <td>endings <td>{{ _profile_endings }}{% endif %}{% if _profile_startings %} <tr> <td>startings <td>{{ _profile_startings }}{% endif %}{% if _profile_comments %} <tr> <td>comments <td>{{ _profile_comments }}{% endif %}{% if _profile_collapse %} <tr> <td>collapse <td>{{ _profile_collapse }}{% endif %}{% if _profile_clippings %} <tr> <td>clippings <td>{{ _profile_clippings }}{% endif %} </table>{% endif %}{% endif %} diff --git a/_layouts/default.html b/_layouts/default.html @@ -0,0 +1,28 @@ +--- +layout: compress +--- + +<!DOCTYPE html> +<html lang="en" > + {% include head.html %} + <body> + <main> + <a href="/" style="width: 100%; text-decoration: none;"> + <h1 style="margin-bottom: 5px;"> + N-Channel MOSFET + </h1> + <h2 style="margin-top: 0px; text-wrap: balance;"> + <i> + Metal–Oxide–Semiconductor Field-effect Transistor + </i> + </h2> + </a> + + {% include navigation.html %} + + {{ content }} + + </main> + + </body> +</html> diff --git a/_layouts/page.html b/_layouts/page.html @@ -0,0 +1,11 @@ +--- +layout: default +--- + +<section class="post"> + <h2>{{ page.title }}</h2> + {{ content }} +</section> + + + diff --git a/_layouts/post.html b/_layouts/post.html @@ -0,0 +1,12 @@ +--- +layout: default +--- + +<section class="post"> + <h1>{{ page.title }}</h1> + {{ content }} + <span class="meta"><time datetime="{{ page.date | date_to_xmlschema }}">{{ page.date | date: "%B %-d, %Y" }}</time> &middot; {% for tag in page.tags %} + <a href="/tag/{{tag}}">{{tag}}</a>{% unless forloop.last %}, {% endunless %}{% endfor %}</span> +</section> + + diff --git a/_layouts/search.html b/_layouts/search.html @@ -0,0 +1,27 @@ +--- +layout: default +title: Search +--- + +<!-- Html Elements for Search --> +<div id="search-container"> +<input type="text" id="search-input" placeholder="search..."> +<ul id="results-container"></ul> +</div> + +<!-- Script pointing to search-script.js --> +<script src="/search.js" type="text/javascript"></script> + +<!-- Configuration --> +<script> +SimpleJekyllSearch({ + searchInput: document.getElementById('search-input'), + resultsContainer: document.getElementById('results-container'), + json: '/search.json', + searchResultTemplate: '<li><a href="{url}" title="{description}">{title}</a></li>', + noResultsText: 'No results found', + limit: 10, + fuzzy: false, + exclude: ['Welcome'] +}) +</script> diff --git a/_layouts/tag.html b/_layouts/tag.html @@ -0,0 +1,17 @@ +--- +layout: default +--- + +<section class="posts"> + <h1>Tag #{{page.tag}}</h1> + <ul> + {% for post in site.posts %} + {% if post.tags contains page.tag %} + <li {% cycle 'alternate-color': 'style="color: #799f7c;"', 'style="color: #b3b781;"'%} > + <a style="color: inherit" class="post" href="{{ post.url }}">{{ post.title }}</a><time datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date: "%m-%d-%Y" }}</time> + </li> + {% endif %} + {% endfor %} + </ul> + +</section> diff --git a/_layouts/tags.html b/_layouts/tags.html @@ -0,0 +1,15 @@ +--- +layout: default +title: Tags +--- + +<section class="posts"> + <h1>{{ page.title }}</h1> +{% assign tags = site.tags | sort %} +<ul> +{% for tag in tags %} + <li><a href="/tag/{{ tag | first | slugify }}/">{{ tag[0] | replace:'-', ' ' }} ({{ tag | last | size }}){% unless forloop.last %}, {% endunless %}</a></li> +{% endfor %} +</ul> + +</section> +\ No newline at end of file diff --git a/_pages/about.md b/_pages/about.md @@ -0,0 +1,24 @@ +--- +layout: page +title: About +--- + +# Hi + +I usually go by Drava, but my real name is Halvard Samdal. +I'm from the second largest city in Norway called Bergen. + +I mostly do systems programming and electronics, so that's what you will see here. +I'm currently a year away from completeing my Bachelor's, but I have a full-time job doing programming and electronics for some guys making arcade machines. + +<br> + +Here's my [github](https://github.com/Samdal/) account if you're interested. I don't really have any non-private socials. + +Even if you already know programming, you should check out these people: +- [Ryan Fleury's Blog](https://www.rfleury.com/) + - [(and read the source code of his projects)](https://github.com/EpicGamesExt/raddebugger) +- [Handmade Hero Videos](https://guide.handmadehero.org/) +- [Casey Muratori's Other vidoes](https://www.youtube.com/@MollyRocket) +- Jonathan Blow +- [John Jackson](https://www.youtube.com/@johnjackson9767) (awesome guy) diff --git a/_pages/search.md b/_pages/search.md @@ -0,0 +1,55 @@ +--- +layout: page +title: Search +--- + +<style> + #search-container { + max-width: 100%; + } + + input[type=text] { + font-size: normal; + outline: none; + padding: 1rem; + background: rgb(236, 237, 238); + width: 100%; + -webkit-appearance: none; + font-family: inherit; + font-size: 100%; + border: none; + } + #results-container { + margin: .5rem 0; + } +</style> + +<!-- Html Elements for Search --> +<div id="search-container"> +<input type="text" id="search-input" placeholder="Search..."> + +<p> <b>Tags: </b> +{% for tag in site.tags %} +<a href="/tag/{{ tag | first }}"> {{ tag | first }}{% unless forloop.last %},{% endunless %}</a> +{% endfor %} +</p> + +<ol id="results-container"></ol> +</div> + +<!-- Script pointing to search-script.js --> +<script src="/search.js" type="text/javascript"></script> + +<!-- Configuration --> +<script type="text/javascript"> +SimpleJekyllSearch({ + searchInput: document.getElementById('search-input'), + resultsContainer: document.getElementById('results-container'), + json: '/search.json', + searchResultTemplate: '<li><a href="{url}" title="{description}">{title}</a></li>', + noResultsText: 'No results found', + limit: 10, + fuzzy: true, + exclude: ['Welcome'] +}) +</script> diff --git a/_plugins/tags.rb b/_plugins/tags.rb @@ -0,0 +1,26 @@ +module Jekyll + class TagPageGenerator < Generator + safe true + + def generate(site) + tags = site.posts.docs.flat_map { |post| post.data['tags'] || [] }.to_set + tags.each do |tag| + site.pages << TagPage.new(site, site.source, tag) + end + end + end + + class TagPage < Page + def initialize(site, base, tag) + @site = site + @base = base + @dir = File.join('tag', tag) + @name = 'index.html' + + self.process(@name) + self.read_yaml(File.join(base, '_layouts'), 'tag.html') + self.data['tag'] = tag + self.data['title'] = "Tag: #{tag}" + end + end +end +\ No newline at end of file diff --git a/_posts/2024-01-20-Simplifying-state.md b/_posts/2024-01-20-Simplifying-state.md @@ -0,0 +1,360 @@ +--- +layout: post +title: "Simplifying state: Synchronous hardware ideas applied to software" +description: Synchronous hardware ideas applied to software +summary: If you have a 'frame' based execution loop, where each module is ran once per frame. You can pass signals around that have a lifetime of a single frame. +comments: true +tags: [writing, electronics, programming, VHDL, C, graphics] +--- + +## State madness + +Lots of code, contains a ton of state. My current project is especially prone due to it being very rule-driven. + +The problem that arises, is as the codebase grows, you naturally end up having multiple systems interacting with each-other. + +Now in my special case, everything is completely compiletime known. Meaning there isn’t really any dynamic modularity to handle, but that makes this example even easier to show. + +We’re going to look at one particular approach to solving complex communication between different “entities”, inspired by my experiences from writing hardware systems. + +## Just use if statements +``` +#include “room1.h” + +void update() { + if (room1.lever1) { + door.state = OPEN; + } else { + door.state = CLOSED; + } +} +``` +Some might think the above code is too simple, or silly. One might say “It’s natural for most systems to grow to some complexity level where you need handlers, objects, etc. globals are bad for extensibility“. + +However, I think this is blatantly false. If you want to get work done, you better try to write things in a way which is this simple. Over-complicating things is a super easy trap to fall into. + +``` + if (((room1.lever1 || room1.lever2) && + (room2.lever1 && room2.lever4)) || + secret_room.master_lever) { + door.state = OPEN; +} else { + door.state = CLOSED; +} +``` +As things get more complex, don’t add some weird system, just continue to do simple things. Imagine writing the snippet above using said weird system where you push changes, or communicate via functions. +If possible you want `door.state` to only be changed at one place. This has the added benefit of being extremely readable. However, you will loose context other places, but if you’re good at grepping that shouldn’t be a problem. + +*Even in situations where less information is known at compiletime, this can be applied in some form. For example, if you don’t know which switches are bound to which doors it’s generally solved by iterating through some datastructures. It’s worth noting that in dynamic cases, you don’t usually have lots of weird cases, they tend to be more predictable.* + +## Wait, all logic inside “update()”? + +Yes, that’s right. Just run all your logic each frame. Computers are blazingly fast, checking some if statements doesn’t take that long. You’re probably going to be limited by other things before a bunch of branches limit you. + +Compared to signals, event systems, and other methods of solving this problem, just having an update functions allows a lot more flexibility. Using event systems leads to exponentially more code as you add states and signals. +A lot of them also use string identifiers, and dynamic access. Which is extremely bothersome for extensibility, and is prone to typos. + +However, one thing should be noted, handling state with just globals + `update()` does have one limitation when it comes to being able to write concise code. +This is when you want code to vary with state, and not just data. +You can fix this by having a switch statement, using function pointers, or even more if statements. + +But there is a better way + +## Corutines + +You can create a corutine instance for each of your update() functions. + +This lets you store code state without having to write lots of code managing it. Personally I use [minicoro](https://github.com/edubart/minicoro) to do my corutine states. + +``` +void update(coro_t* co) { + while (true) { + if (room1.lever1) break; + yield(co); + } + while (true) { + if (room1.lever2 || room2.lever1) break; + u32 res = do_update_1(); + if (res > 3) do_update_2(); + yield(co); + } + while (true) { + if (room4.lever3 && room4.push_button) break; + do_update_2(); + yield(co); + } + open_final_door(); +} +``` + +I’m hoping this simple example sort of shows how extensible and concise the code gets by doing this. Even very stateful programs can be implemented this way, and the huge benefit is that you can follow the code linearly downwards. It’s also extremely easy to debug, as you can just step through each corutines `update()` function. + +## When it gets bothersome + +What happens if we want to only act on changes to some of these variables? + +``` +void update(coro_t* co) { + bool has_pushed_lever1 = false; + while (true) { + if (!hash_pushed_lever1 && room1.lever1) { + play_audio(&audio_sources[AUDIO_LEVER_OPEN]); + has_pushed_lever1 = true; + } + if (hash_pushed_lever1 && !room1.lever1) { + play_audio(&audio_sources[AUDIO_LEVER_CLOSE]); + has_pushed_lever1 = false; + } + yield(co); + } +} +``` +You start having to do stuff like this. + +You might get the idea of using some event system just for this. Now it generally gets the job done, but as explained it gets hard to manage. +Event systems play very poorly if you want situations like explained further above, where you have lots of things in your if statements. Having to set up event listeners for each one of those and somehow collecting them together is how you get horrible spaghetti code. +Callbacks also don’t allow you to change a corutine’s state in any meaningful way, which is even more of a problem. This means you will have to go back to switch statements again. How horrid! + +You probably already know the solution to this. It is to just store the previous state of each such variable. +``` +typedef struct { + bool state; + bool last_state; +} lever_t; +``` + +If you have ever used or made a platform library that isn’t event based, you have probably encountered this for button presses or the like: +`if (platform_key_pressed(KEYCODE_SPACE) == true)` + +It’s essentially the same concept. Each frame, only one place edits the lever’s data, meaning that as long as every single piece of code is ran each frame, you can just check if the state has changed. + +## How this is related to synchronous hardware? +When making synchronous hardware, you often have control signals which only exist for a single “clock-cycle”. + +Take this VHDL code for blinking an LED at 1Hz as an example + +``` +clock_enable_1hz_gen : enable_gen port map ( + clk_50hz => CLOCK, resetn => NRESET, + clock_divisor => “00000001011111010111100001000000”, + enable => clock_enable_1hz + ); + +alive_gen : process (CLOCK, NRESET) is +begin + if rising_edge(CLOCK) then + if NRESET = ‘0’ then + LEDR(0) <= ‘0’; + elsif clock_enable_1hz = ‘1’ then + LEDR(0) <= not LEDR(0); + end if; + end if; +end process; +``` +This process is synchronous because it only does something during the rising edge of the clock signal. clock_enable_1hz is only on during one clock cycle, and is enabled every nth rising edge, such that we can toggle the LED at 1Hz. +This technique is riddled across hardware design. When designing hardware, everything is ran each clock-cycle. Everything is an update() function. + +This is essentially the same as our software, where we run each update function each “frame” instead of each “clock-cycle” + +This constraint of hardware design, gives life to this unique way of sharing stateful information. And gives us a feature we can exploit when writing software as well. + +## Using this knowledge +``` +bool secret_hold_button_click; + +void update(coro_t* co) { + float hold_time = 0; + while (true) { + secret_hold_button_click = false; // always reset + + if (room.secret_hold_button[0].state == true) + hold_time += delta_time(); + else + hold_time = 0; + + if (hold_time > 1.0) { + secret_hold_button_click = true; + hold_time = 0; + } + + yield(co); + } +} + +// somewhere else... +void update_special_room() { + if (secret_hold_button_click && special_mode_active) + play_audio(&audio[AUDIO_SPECIAL_ROOM]); +} +``` +We can propagate control signals through our “synchronous” codebase just by having variables that have a per-frame lifetime. + +I’ve found this extremely powerful. It’s simpler and more concise than an event system. It allows you to still use corutines, and is thereby easy to debug. It also creates manageable codepaths, and you can inspect the memory to see which states are active at any given moment. + +This is not limited to booleans either. We can for example use counters. Or for more advanced info, you might want to to create an array instead. +``` +struct room_t { + s32 changes_this_frame; // signed on purpose + u32 people_in_room; +} + +void update_room(coro_t* co, room_t* room) { + s32 in_room_prev = 0; + + while (true) { + room.people_in_room = count_people_in_room(room1); + room.changes_this_frame = (s32)room.people_in_room - in_room_prev; + in_room_prev = people_in_room; + yield(co); + } +} +``` + +## Comparison to callback driven system + +### Simple condition +``` +void init() { + event_add_listner(room.levers[0].on_click, try_open_room_secret); +} + +ev_func(try_open_room_secret) { + if (room.has_secret) { + room.secret_doors[0] = OPEN; + event_del_listner(room.levers[0].on_click, try_open_room_secret); + } +} +``` +vs +``` +void update(coro_t* co) { + while (true) { + if (room.has_secret && lever_pressed(room.levers[0])) { + room.secret_doors[0] = OPEN; + return; + } + yield(co); + } +} +``` + +As you can see, they’re fairly comparable currently. You could even say the event system has some benefits. +### More complex conditions +*You need to press multiple buttons within a time frame.* + +``` +timer_t* room_timer; +bool lever_states[2]; + +void init() { + for (u32 i = 0; i < count_of(lever_states); i++) + event_add_listner(room.levers[i].on_click, try_open_room_secret); + room_timer.init(); + event_add_listner(room_timer.on_timeout, reset_levers); +} + +ev_func(try_open_room_secret) { + // lets hope the event poster gives information about which lever was pressed... + lever_t* lever = (lever_t*)ev_data; + + if (!room_timer.timer_started) { + room_timer.start_timer(10.0); + } + lever_states[lever.index] = true; + bool all_pressed = true; + for (u32 i = 0; i < count_of(lever_states); i++) { + if (!lever_states[i]) { + all_pressed = false; + break; + } + } + if (all_pressed) { + room.secret_doors[0] = OPEN; + for (u32 i = 0; i < count_of(lever_states); i++) + event_del_listner(room.levers[i].on_click, try_open_room_secret); + event_del_listner(room_timer.on_timeout, reset_last_lever); + room_timer.destroy(); + } +} + +ev_func(reset_levers) { + lever_states = {0}; +} +``` +vs +``` +void update(coro_t* co) { + while (true) { + bool lever_states[2] = {0}; + for (u32 i = 0; i < count_of(lever_states); i++) + if (lever_pressed(room.levers[i]) goto lever_pressed; + yield(co); + continue; + +lever_pressed: + for (yield_for_seconds(co, 10.0)) { + bool all_pressed = true; + for (u32 i = 0; i < count_of(lever_states); i++) { + if (lever_pressed(room.levers[i]) lever_states[i] = true; + if (!lever_states[i]) all_pressed = false; + } + if (all_pressed) { + room.secret_doors[0] = OPEN; + return; + } + } + } +} +``` +The event solution even requires us to create a new “timer” module. Meanwhile, corutines can naturally yield for a set amount of time just by checking what the current time is. + +The event solution also has more management around it, requiring us to initialize the timer and event systems, as well as cleaning them up once we’re done with them. + +This only gets worse for the event driven solution once you add multiple types of events. You will have to pull your code into even more functions, and do checks from data accumulated from different event handlers. +The corutine solution doesn’t have to change—It is 100% extensible, we have yet to introduce any abstractions on it. + +## Doing it the other way around + +We can also read at the reset point, and allow other code to set the data. +```c +struct entity_t { + vec2 pos; + int health; + bool on_fire; + bool immune_to_fire_this_frame; +}; + +void fire_immunity_circle(coro_t* co) { + vec2 circle_centre; + float radius; + while (true) { + for (e in entities) { + if (vec2_dist(e.pos, circle_centre) <= radius) { + e.immune_to_fire_this_frame = true; + } + } + yield(co); + } +} + +void update_entity(entity_t* e) { + if (e.on_fire) { + if (!e.immune_to_fire_this_frame) + e.health -= 1; + } + e.immune_to_fire_this_frame = false; +} +``` + +The above code makes it so you don’t have to track which entities are inside your circle, to disable immunity on their eventual exit. You also don’t have problems if the immunity fields are intersecting. + +The other way to achieve the same would be to loop over all immunity fields inside `update_entity()`. This would indeed work. However, it means you have to store all your immunity fields in some array(s). Toggling, enableing and doing weird things would get increasingly constrained. + +The above code gives you full control over how the immunity fields are allocated and applied. You can have weird logic like only applying to entities on a team, or who are in the same building, or who are named in some certain way. All without changing the interface to towards the entities. It’s really flexible. + +<br> + +--- + +## Closing thoughts +I believe that using checking logic each frame—combined with corutines and synchronous control signals—is an extremely powerful and simple way to implement heavy and intertwined logic. diff --git a/_posts/2024-05-08-VGA-Kontroller.md b/_posts/2024-05-08-VGA-Kontroller.md @@ -0,0 +1,690 @@ +--- +layout: post +title: "[Norwegian] VGA Kontroller" +description: VGA Kontroller +summary: Oppbygning av en enkel VGA kontroller fra bunnen av +comments: true +tags: [writing, electronics, programming, VHDL, C] +--- + +## Forord +Dette er utsnitt fra en proskjektoppgave jeg gjorde i det andre FPGA faget jeg hadde I løpet av Bacheloren min. + +## Introduksjon +Denne bloggen går ut på å implementere en kommunikasjonsprotokoll i fastvare, og binde det til en NIOS II CPU, ved hjelp av DE2-115 FPGA utviklerkortet. + + +### VGA protokollen +> **Video Graphics Array (VGA)** is a video display controller and accompanying de facto graphics standard, first introduced with the IBM PS/2 line of computers in 1987, which became ubiquitous in the IBM PC compatible industry within three years. The term can now refer to the computer display standard, the 15-pin D-subminiature VGA connector, or the 640x480 resolution characteristic of the VGA hardware. +> +> \- [Video Graphics Array, Wikipedia](https://en.wikipedia.org/wiki/Video_Graphics_Array) + +VGA har lenge vært brukt i industrien. Det er en analog standard som er relativt timing kritisk. Videoprotokollere krever at du sender informasjon hele tiden, dette betyr at å sende hver byte fra CPU vil bruke nesten 100% av utførelsestiden. I tillegg vil timing sannsynligvis være et problem ved enkelte oppløsninger og oppfriskningshastigheter. Å ha diskret fastvarekomponenter som står mellom CPU og VGA er derfor nødvendig. + +### Trinnene som skal beseires +1. Forståelse av VGA-protokollen. Hvordan fungerer den? +1. Implementasjon av protokollen i VHDL. Kan vi få en skjerm til å vise en enkel farge? +1. Tyngre testing. Kan vi lage et avansert mønster som flytter seg? +1. Simple Buss Interface. Kan vi tegne fra NIOS II, har vi nok VRAM? +1. Text-mode i fastvare. Hvordan ble minne og hastighet løst før i tiden? + +## Forståelse av VGA +Mesteparten av ledningene i VGA kontakten er GND eller ikke nødvendig til grunnleggende bruk. I DE2-115 kortet er bare RGB, horisontal sync og vertikal sync koblet. Siden R, G, og B er analoge signaler, krever vi en DAC for å omgjøre signalene. + +![Figur 1](/assets/images/VGA/VGA_fig1.jpeg) +{: style="margin-left: auto; margin-right: auto"} +Figur 1. DE2-115 VGA schematic +{: style="color:gray; font-size: 80%; text-align: center;"} + +På DE2-115 kortet så brukes det en 3 kanals 10-bit DAC som er spesielt laget for høy-hastighet videosignaler. Bare 8 av de 10 mulige bits-ene blir faktisk brukt, som vi kan se i skjemategningen. De to nederste bits-ene er alltid 0, så den minste ikke-null lysstyrken våres er faktisk høyere enn det en kanskje hadde forventet. + +I VHDL må vi bare sette opp en `std_logic_vector(7 downto 0)` for hver av disse signalene, og så legge inn et 8-bit tall på hver av dem. + +ADV7123 chip-en som er på kortet har og koblet noen signaler for klokke, sync og blank. Klokke signalet kan vi bare koble til samme klokke vi bruker til å styre RGB-signalene. Sync og blank har vi ikke bruk for. Så blank kan vi bare holde høy, og sync kan vi holde lav. + +![Figur 2](/assets/images/VGA/VGA_fig2.jpeg) +{: style="margin-left: auto; margin-right: auto; margin-bottom: 0px; padding-bottom: 0px"} +Figur 2. ADV7123 block diagram. +{: style="color:gray; font-size: 80%; text-align: center;"} + +Å bare sende farger er selvfølgelig ikke nok informasjon for skjermen å vite hvilke oppløsning og oppfriskningshastighet som brukes. I tillegg krever skjermen informasjon om hvilken kolonne og rad vi faktisk ønsker å tegne til. I VGA sendes dette gjennom `h_sync` og `v_sync` signalene. Disse signalene aktiveres under det som kalles “blanking period”. Altså, vi tegner ikke til skjermen 100% av tiden. + +![Figur 3](/assets/images/VGA/VGA_fig3.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 3. VGA timing diagram +{: style="color:gray; font-size: 80%; text-align: center;"} + +Timingen består da av 4 perioder for hver kolonne, og likeledes 4 perioder for radene samlet. Den enkleste måten å finne hvor lenge hver av disse periodene skal være, er å bla de opp i en tabell. + +![Figur 4](/assets/images/VGA/VGA_fig4.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 4. VGA timing tabell. +{: style="color:gray; font-size: 80%; text-align: center;"} + +Som vi kan se her er det en tredje parameter vi ikke har tatt i betenkning enda, “Polarity”. Alt polarity gjør er å invertere `h_sync` og `v_sync` signalene. + +Med all informasjonen våres samlet, så kan vi formalisere logikken litt mer. + +*pseudo-kode for VGA timing:* +``` +let h_sync = 1; +let v_sync = 1; +const total_v_time = height+v_front_porch+v_sync_pulse+v_back_porch; +const total_h_time = width+h_front_porch+h_sync_pulse+h_back_porch; +const total_time = total_v_time * total_h_time; +let h_counter = 0; +let v_counter = 0; +while True do + if h_counter >= total_h_time-1 then + h_counter = 0; + if v_counter >= total_v_time-1 then + v_counter = 0; + else + v_counter++; + end; + else + h_counter++; + end; + + if h_counter >= width+h_front_porch and + h_counter < width+h_front_porch+h_sync_pulse then + h_sync = h_polarity; + else + h_sync = not h_polarity; + end; + if v_counter >= height+v_front_porch and + v_counter < height+v_front_porch+v_sync_pulse then + v_sync = v_polarity; + else + v_sync = not v_polarity; + end; + + wait 1; +end; +``` + +Så langt er det rett fram, la oss gå over til VHDL. + +## Implementasjon i VHDL +``` +vga_process : process (pixel_clock) is + variable h_counter : integer range 0 to h_period - 1 := 0; + variable v_counter : integer range 0 to v_period - 1 := 0; +begin + if rising_edge(pixel_clock) then + if nreset = '0' then + h_counter := 0; + v_counter := 0; + draw_time <= '0'; + pan <= 0; + vga_hs <= not h_polarity; + vga_vs <= not v_polarity; + else + -- inkrementer tellere, først horisontal scanlinje, så neste linje + if h_counter = h_period - 1 then + h_counter := 0; + if v_counter = v_period - 1 then + v_counter := 0; + if pan = pan_max then + pan <= 0; + else + pan <= pan + 1; + end if; + else + v_counter := v_counter + 1; + end if; + else + h_counter := h_counter + 1; + end if; + -- hold vsync/hsync low/high dersom vi er i sync området + if h_counter < h_pixels + h_front_porch or + h_counter >= h_pixels + h_front_porch + h_sync_pulse then + vga_hs <= not h_polarity; + else + vga_hs <= h_polarity; + end if; + if v_counter < v_pixels + v_front_porch or + v_counter >= v_pixels + v_front_porch + v_sync_pulse then + vga_vs <= not v_polarity; + else + vga_vs <= v_polarity; + end if; + if h_counter < h_pixels then + x <= h_counter; + else + x <= 0; + end if; + if v_counter < v_pixels then + y <= v_counter; + else + y <= 0; + end if; + if h_counter < h_pixels and v_counter < v_pixels then + draw_time <= '1'; + else + draw_time <= '0'; + end if; + end if; + end if; +end process; +``` + +Koden handler stort sett bare om noen tellere, og å skru på draw_time, h_sync og v_sync i de riktige periodene. Koden bruker konstanter/generics, så det er enkelt å endre på timing. Men så langt er det ikke planlagt å ha mulighet for å endre timing dynamisk fra NIOS II. + +![Figur 5](/assets/images/VGA/VGA_fig5.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 5. Skjerm koblet til DE2-115 kort. +{: style="color:gray; font-size: 80%; text-align: center;"} + + +![Figur 6](/assets/images/VGA/VGA_fig6.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 6. Skjerm med figurer som endrer seg +{: style="color:gray; font-size: 80%; text-align: center;"} +``` +if ((x + pan / 1) / 32) mod 2 = 0 and ((y + pan / 1) / 32) mod 2 = 0 then + vga_r <= (others => '1'); + vga_g <= (others => '0'); + vga_b <= (others => '1'); +else + vga_r <= (others => '0'); + vga_g <= (others => '1'); + vga_b <= (others => '1'); +end if; +``` + +## Tilkobling av RAM modul +Første steget her er å flytte ut informasjon om x, y, og draw_time utenfor vga-modulen. +Deretter skriver vi en enkel test for å se at det fortsatt fungerer +``` +draw : process(CLOCK_25) is +begin + if rising_edge(CLOCK_25) then + if draw_time = '1' then + VGA_R <= (others => '1'); + VGA_G <= (others => '1'); + VGA_B <= (others => '1'); + if x < 100 then + VGA_R <= (others => '1'); + VGA_G <= (others => '0'); + VGA_B <= (others => '1'); + end if; + else + VGA_R <= (others => '0'); + VGA_G <= (others => '0'); + VGA_B <= (others => '0'); + end if; + end if; +end process; +``` +Dette gir lilla kolonne, og resten hvit, slik som forventet. Alt fungerer fortsatt. + +Vi legger så til en ram modul gjennom IP-katalogen, vi trenger en 2-port ram, med egen klokke for read/write. + +For å støtte hele fargespekteret, trenger vi 8(bits) * 3(farger) * 640(bredde) * 480(høyde) +altså 7372800 bits. +Vi velger en ord-størrelse på 24-bit, slik at vi kan lese alle tre fargene om gangen. Dette betyr 307200 ord totalt. + + +![Figur 7](/assets/images/VGA/VGA_fig7.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 7. 2-port ram fra IP-katalog +{: style="color:gray; font-size: 80%; text-align: center;"} + + +![Figur 8](/assets/images/VGA/VGA_fig8.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 8. Kompilasjons rapport, minneforbruk +{: style="color:gray; font-size: 80%; text-align: center;"} + +Men som en kan se er det ikke nok plass til å ha 8:8:8 farger ved 640x480 oppløsning. +Vi er nødt til å gjøre forenklinger. +Dersom vi deler oppløsningen på 8, får vi da 80x60 * 24, som er 115200 bits. +Det skal være godt innenfor. + +Vi kobler RAM modulen +``` +vram_instance : vram port map( + wrclock => CLOCK_50, + wraddress => vram_wraddress, + data => vram_data, + wren => vram_wren, + + rdclock => CLOCK_25, + rdaddress => vram_rdaddress, + q => vram_out +); + + +vram_rdaddress <= std_logic_vector(to_unsigned( + (h_pixels / 8) * (y / 8) + ((x+1) / 8), 13)); +VGA_R <= vram_out(23 downto 16) when draw_time = '1' else (others => '0'); +VGA_G <= vram_out(15 downto 8) when draw_time = '1' else (others => '0'); +VGA_B <= vram_out(7 downto 0) when draw_time = '1' else (others => '0'); +``` +[grunnen til at det er (x+1) dekkes i neste side] +{: style="color:gray; font-size: 80%; text-align: center;"} + +process til testing av VRAM i VHDL, brukt sammen med signal tap logic analyzer. +``` +fill_color : process(CLOCK_50) is + variable i : integer range 0 to v_pixels * h_pixels / 8; + variable slow : integer range 0 to 500000; +begin + if rising_edge(CLOCK_50) then + vram_wren <= '0'; + if KEY(0) = '0' then + i := 0; + slow := 0; + end if; + if slow = 500000 and KEY(1) = '0' then + slow := 0; + if i < (v_pixels*h_pixels/8-1) and SW(17) = '1' then + vram_wren <= '1'; + vram_wraddress <= std_logic_vector(to_unsigned(i, 13)); + + if SW(0) = '1' then + vram_data <= "111111110000000000000000"; + elsif SW(1) = '1' then + vram_data <= "000000001111111100000000"; + elsif SW(2) = '1' then + vram_data <= "000000000000000011111111"; + else + vram_data <= "000000000000000000000000"; + end if; + + i := i + 1; + end if; + else + slow := slow + 1; + end if; + end if; +end process; +``` +Siden systemet er synkront, trenger vi å laste inn fargen en klokkesyklus før den vises. Dette kan gjøres med å utvide rekkevidden til x integer til å inkludere -1, deretter endre på hvordan tellerne oppfører seg når det er blanking time. +``` +-- I VGA entity + if h_counter < h_pixels then + x <= h_counter; + else + x <= -1; + end if; + if v_counter < v_pixels then + y <= v_counter; + if h_counter >= h_pixels and v_counter + 1 < v_pixels then + y <= v_counter + 1; + end if; + else + y <= 0; + end if; +``` +Dersom vi ikke gjør dette, får vi følgende problemer + + +![Figur 9](/assets/images/VGA/VGA_fig9.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 9. indeksering problemer som oppsto underveis +{: style="color:gray; font-size: 80%; text-align: center;"} + +Til slutt så har vi et minneområde vi kan tegne rød/grønn/blå/svart til. + +![Figur 10](/assets/images/VGA/VGA_fig10.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 10. Sluttresultat av VRAM testing i VHDL, alle farger har blitt tegnet på med vilje. +{: style="color:gray; font-size: 80%; text-align: center;"} + +## Interface mot NIOS II +Vi lager et enkelt bus interface + +``` +entity main is + port( + CLK : in std_logic; + RESET : in std_logic; + CHIPSELECT : in std_logic; + WR : in std_logic; + RD : in std_logic; + ADDRESS : in std_logic_vector(12 downto 0); + WRITEDATA : in std_logic_vector(31 downto 0); + READDATA : out std_logic_vector(31 downto 0); + + VGA_R, VGA_G, VGA_B : out std_logic_vector(7 downto 0); + VGA_CLK, VGA_SYNC_N, VGA_BLANK_N : out std_logic; + VGA_VS, VGA_HS : out std_logic + ); +end entity main; +``` +``` +bus_interface : process (CLK) is +begin + if rising_edge(CLK) then + vram_wren <= '0'; + if nreset = '0' then + elsif CHIPSELECT = '1' then + if WR = '1' then + vram_wren <= '1'; + vram_data <= WRITEDATA(23 downto 0); + vram_wraddress <= ADDRESS(12 downto 0); + end if; + end if; + end if; +end process; +``` +Også legger vi til denne i platform designer + +![Figur 11](/assets/images/VGA/VGA_fig11.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 11. vga kontroller i platform designer +{: style="color:gray; font-size: 80%; text-align: center;"} + +Vi lager så programmer i C som rett og slett bare skriver til riktig adresse. + +Gradvis fylling: +``` +for (;;) { + for (uint32_t c = 0; c < 3; c++) { + for (uint32_t i = 0; i < 80*60; i++) { + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, i*4, 0xff << (c*8)); + usleep(100); + } + } +} +``` +Gradient +``` +for (uint32_t y = 0; y < 60; y++) { + uint32_t c = (y * 255/60); + for (uint32_t x = 0; x < 80; x++) { + if (x > 80 * 2/3) { + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<16); + } else if (x > 80 * 1/3) { + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<8); + } else { + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c<<0); + } + } +} +``` +Bilde +``` +for (uint32_t i = 0; i < 80*60; i++) { + uint32_t p = 0; + p |= gimp_image.pixel_data[i*3+0] << 16; + p |= gimp_image.pixel_data[i*3+1] << 8; + p |= gimp_image.pixel_data[i*3+2] << 0; + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, i*4, p); +} +``` + + +![Figur 12](/assets/images/VGA/VGA_fig12.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 12. farger tegnet fra NIOS II +{: style="color:gray; font-size: 80%; text-align: center;"} + + +![Figur 13](/assets/images/VGA/VGA_fig13.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 13. Bilde eksportert til C-kildekode fra GIMP +{: style="color:gray; font-size: 80%; text-align: center;"} + +## VGA Text-Mode + +> **VGA text mode** was introduced in 1987 by IBM as part of the VGA standard for its IBM PS/2 computers. Its use on IBM PC compatibles was widespread through the 1990s and persists today for some applications on modern computers. The main features of VGA text mode are colored (programmable 16 color palette) characters and their background, blinking, various shapes of the cursor (block/underline/hidden static/blinking), and loadable fonts (with various glyph sizes). The Linux console traditionally uses hardware VGA text modes, and the Win32 console environment has an ability to switch the screen to text mode for some text window sizes. +> +> \- [VGA_text_mode, Wikipedia](https://en.wikipedia.org/wiki/VGA_text_mode) + +VGA text mode lar deg bruke et 8x16 font-atlas til å tegne på skjermen. font-atlaset består av et enkelt bitmap, du velger selv hvilke farger som skal være i forgrunnen og bakgrunnen. +Kommunikasjon med en VGA text-mode enhet skjer i 16-bit ord, hvor hvert ord representerer en karakter. + +![Figur 14](/assets/images/VGA/VGA_fig14.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 14. Vga text mode oppbygning (fra wikipedia) +{: style="color:gray; font-size: 80%; text-align: center;"} + +![Figur 15](/assets/images/VGA/VGA_fig15.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 15. 4-bit og 3-bit fargepalett. +{: style="color:gray; font-size: 80%; text-align: center;"} + +4-bit fargepalletet ble hentet fra CGA grafikkortet, mens 3-bit fargepalett ble hentet fra European Computer Manufacturers Association standarden stiftet i 1976. + +<br> + +Font-atlaset lagrer vi i en gigantisk `std_logic_vector`, det er langt ifra optimalt, men det lar oss unngå timing problemer under lesing av atlaset. +``` +signal bitmap : std_logic_vector(0 to 128*256-1); +``` +Det første vi gjør er å utvide adresse-mappet, slik at vi gir brukeren mulighet til å endre på font-atlaset, samt mulighet til å velge om text-mode eller full-color skal brukes. + +``` +bus_interface : process (CLK) is + variable curr_32_bit_addr : integer; +begin + if rising_edge(CLK) then + vram_wren <= '0'; + if nreset = '0' then + is_vga_text_mode <= '0'; + elsif CHIPSELECT = '1' then + if address_integer < 4800 then + if WR = '1' then + vram_wren <= '1'; + vram_wraddress <= ADDRESS(12 downto 0); + vram_data <= WRITEDATA(23 downto 0); + end if; + elsif address_integer = 4800 then + if WR = '1' then + is_vga_text_mode <= WRITEDATA(0); + end if; + if RD = '1' then + READDATA <= "0000000000000000000000000000000" & is_vga_text_mode; + end if; + elsif address_integer < 4800 + 128*256 then + if WR = '1' then + curr_32_bit_addr := (address_integer-(4800+1)) / 32; + bitmap(curr_32_bit_addr*32 to (curr_32_bit_addr+1)*32 - 1) <= WRITEDATA; + end if; + end if; + end if; + end if; +end process; +``` + +Vi bør og fylle inn en default-verdi i font-atlaset. Dersom du søker på nettet, er de fleste ressurser skrevet for DOS. Heldigvis har vi utrolig gode emulatorer som DOSBox nå til dags. + +![Figur 15](/assets/images/VGA/VGA_fig16.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 16. Fontraption kjørende i DOSBox +{: style="color:gray; font-size: 80%; text-align: center;"} + +Dette lar oss eksportere mangfoldige text-mode skrifttyper til flere formater. + +For å automatisk generere en `std_logic_vector` i formatet vi selv ønsker, er vi nødt til å lage et lite skript som leser en bildefil og skriver ut masse tekst. +``` +int main() +{ + for (int i = 0; i < 256; i++) { + int xbegin = (i % 16) * 8; + int ybegin = i / 16 * 16; + for (int y = 0; y < 16; y++) { + printf("\""); + for (int x = 0; x < 8; x++) { + unsigned char b = gimp_image.pixel_data[((ybegin+y)*128 + xbegin+x) * 3]; + printf("%d", b != 0); + } + printf("\" &\n"); + } + printf("\n"); + } +} +``` + +![Figur 17](/assets/images/VGA/VGA_fig17.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 17. font i `std_logic_vector` +{: style="color:gray; font-size: 80%; text-align: center;"} + +Så ikke minst, prosessen for å skrive til skjermen ut ifra hvilken modus. Her ble en asynkron prosess valgt, siden ellers ville timing og klokkedomener blitt et problem. + +``` +mode : process (x, y, draw_time, vram_out, blink_white, bitmap, nreset, is_vga_text_mode) is + variable current_character : std_logic_vector(0 to 127); + variable code_point : integer range 0 to 255; + variable foreground : integer range 0 to 15; + variable background : integer range 0 to 7; + variable blink : std_logic; + + variable code_point_x : integer range 0 to 7; + variable code_point_y : integer range 0 to 15; +begin + VGA_R <= (others => '0'); + VGA_G <= (others => '0'); + VGA_B <= (others => '0'); + vram_rdaddress <= (others => '0'); + if nreset = '0' then + -- nothing + else + + if is_vga_text_mode = '0' then + vram_rdaddress <= std_logic_vector(to_unsigned( + (h_pixels / 8) * (y / 8) + ((x+1) / 8), 13)); + else + vram_rdaddress <= std_logic_vector(to_unsigned( + (h_pixels / 8) * (y / 16) + ((x+1) / 8), 13)); + end if; + + if draw_time = '1' then + if is_vga_text_mode = '0' then + VGA_R <= vram_out(23 downto 16); + VGA_G <= vram_out(15 downto 8); + VGA_B <= vram_out(7 downto 0); + else + + code_point := to_integer(unsigned(vram_out(7 downto 0))); + foreground := to_integer(unsigned(vram_out(11 downto 8))); + background := to_integer(unsigned(vram_out(14 downto 12))); + blink := vram_out(15); + current_character := bitmap(code_point * 128 to (code_point+1) * 128 - 1 ); + VGA_R <= color_map_8(background)(23 downto 16); + VGA_G <= color_map_8(background)(15 downto 8); + VGA_B <= color_map_8(background)(7 downto 0); + + if blink = '0' or blink_white = '1' then + code_point_y := y mod 16; + code_point_x := x mod 8; + if current_character(code_point_y * 8 + code_point_x) = '1' then + VGA_R <= color_map_16(foreground)(23 downto 16); + VGA_G <= color_map_16(foreground)(15 downto 8); + VGA_B <= color_map_16(foreground)(7 downto 0); + end if; + end if; + end if; + end if; + end if; +end process; +``` + +`blink_white` blir laget av en ganske enkel prosess, lignende heart.vhd +``` +blink_white_gen : process(CLK) is +begin + if rising_edge(CLK) then + if nreset = '0' then + blink_counter <= (others => '0'); + else + blink_counter <= blink_counter + 1; + end if; + end if; +end process; +blink_white <= blink_counter(24); +``` +For å teste alle mulige kombinasjoner av farge, så tegner vi “A” på hele skjermen, også endrer vi forgrunn og bakgrunn ut fra x og y koordinat. + +``` +for (uint32_t y = 0; y < 30; y++) { + for (uint32_t x = 0; x < 80; x++) { + int cfg = x * 16 / 80; + uint32_t c = 0 << 15 | ((y * 8/30) << 12) | (cfg << 8) | 65; + IOWR_32DIRECT(VGA_CONTROLLER_0_BASE, (y*80+x)*4, c); + } +} +``` + + +![Figur 18](/assets/images/VGA/VGA_fig18.jpeg) +{: style="text-align: center; margin-bottom: 0px; padding-bottom: 0px"} +Figur 18. VGA Text-Mode +{: style="color:gray; font-size: 80%; text-align: center;"} + +VGA text-mode lar oss utnytte utrolig mye mer oppløsning selv om vi har begrenset minne og prosessorkraft. I tillegg gjør det skriving av software enklere, siden enhver applikasjon ikke trenger å implementere font-rendering. + +## C driver implementasjon +For å forenkle utviklingen av framtidig programvare lager vi en C-driver. Se datablad for mer informasjon om både driveren og for eksempel på bruk. +``` +#include <io.h> +#include <system.h> +#include <inttypes.h> + +typedef union vga_full_color { + struct __attribute__((packed)) { + union __attribute__((packed)) { + struct __attribute__((packed)) { + uint8_t b,g,r; + }; + uint8_t bgr[3]; + }; + uint8_t _padding; + }; + uint32_t raw_dword; +} vga_full_color_t; + +typedef union vga_text_mode { + struct { + uint16_t codepoint : 8; + uint16_t fg : 4; + uint16_t bg : 3; + uint16_t blink : 1; + }; + uint16_t raw_word; +} vga_text_mode_t; + +#define VGA_MODE_FULL_COLOR 0 +#define VGA_MODE_TEXT_MODE 1 +uint8_t vga_get_mode(int base_address) { + return IORD_32DIRECT(base_address, 4800*4); +} +void vga_set_mode(int base_address, uint8_t mode) { + IOWR_32DIRECT(base_address, 4800*4, mode & 1); +} +void vga_clear_screen(int base_address) { + for (uint32_t i = 0; i < 60*80; i++) { + IOWR_32DIRECT(base_address, i*4, 0); + } +} + +void vga_full_color_set(int base_address, uint32_t index, vga_full_color_t c) { + IOWR_32DIRECT(base_address, index*4, c.raw_dword); +} +void vga_full_color_set_xy(int base_address, unsigned char x, unsigned char y, vga_full_color_t c) { + if (x >= 80) { + // wrap + y += x/80; + x %= 80; + } + if (y >= 60) y %= 60; + IOWR_32DIRECT(base_address, (y*80+x)*4, c.raw_dword); +} + +void vga_text_mode_set(int base_address, uint32_t index, vga_text_mode_t c) { + IOWR_32DIRECT(base_address, index*4, c.raw_word); +} +void vga_text_mode_set_xy(int base_address, unsigned char x, unsigned char y, vga_text_mode_t c) { + if (x >= 80) { + // wrap + y += x/80; + x %= 80; + } + if (y >= 30) y %= 30; + IOWR_32DIRECT(base_address, (y*80+x)*4, c.raw_word); +} +``` diff --git a/_posts/2025-02-20-hello-world (copy 1).md b/_posts/2025-02-20-hello-world (copy 1).md @@ -0,0 +1,17 @@ +--- +layout: post +title: Hello World +description: Hello World +#summary: What is the difference between various font formats? +comments: true +tags: [writing] +--- +Hello world + +``` +#include <stdio.h> + +int main() { + puts("Hello World"); +} +``` diff --git a/_posts/2025-02-20-hello-world (copy 2).md b/_posts/2025-02-20-hello-world (copy 2).md @@ -0,0 +1,17 @@ +--- +layout: post +title: Hello World +description: Hello World +#summary: What is the difference between various font formats? +comments: true +tags: [writing] +--- +Hello world + +``` +#include <stdio.h> + +int main() { + puts("Hello World"); +} +``` diff --git a/_posts/2025-02-20-hello-world (copy 3).md b/_posts/2025-02-20-hello-world (copy 3).md @@ -0,0 +1,17 @@ +--- +layout: post +title: Hello World +description: Hello World +#summary: What is the difference between various font formats? +comments: true +tags: [writing] +--- +Hello world + +``` +#include <stdio.h> + +int main() { + puts("Hello World"); +} +``` diff --git a/_posts/2025-02-20-hello-world (copy 4).md b/_posts/2025-02-20-hello-world (copy 4).md @@ -0,0 +1,17 @@ +--- +layout: post +title: Hello World +description: Hello World +#summary: What is the difference between various font formats? +comments: true +tags: [writing] +--- +Hello world + +``` +#include <stdio.h> + +int main() { + puts("Hello World"); +} +``` diff --git a/_posts/2025-02-20-hello-world.md b/_posts/2025-02-20-hello-world.md @@ -0,0 +1,17 @@ +--- +layout: post +title: Hello World +description: Hello World +#summary: What is the difference between various font formats? +comments: true +tags: [writing] +--- +Hello world + +``` +#include <stdio.h> + +int main() { + puts("Hello World"); +} +``` diff --git a/_sass/_main.scss b/_sass/_main.scss @@ -0,0 +1,233 @@ +*,:after,:before { + box-sizing:border-box; + background-color:inherit; + color:inherit; + margin:0; + padding:0; +} + +body { + font-family: serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + line-height:1.5; + font-size: 1rem; + color: rgb(22, 23, 26); + background-color: #faf9ee; +} + +nav ul { + border-right: 3px solid #bcd1cf; +} + +a { + color: #000; + text-decoration-skip-ink: auto; + text-decoration: underline; +} + +pre { + margin: .5rem 0; + padding: .5rem; +} + +.post p { + margin: .5rem 0; +} + +main h1 { + color: #f4ca96; +} +main h2 { + color: #bcd1cf; +} + +.post h1 { + color: #f4ca96; +} +.post h2 { + color: #bcd1cf; +} +.post h3 { + color: #a3bfa3; +} + + +.post h1, .post h2, .post h3, .post h4 { + margin: 1rem 0; +} + +.post h2:first-child, .project h2:first-child, .photo h2:first-child { + margin-top: 0; +} + +.meta { + margin: 2rem 0; +} + +code,pre { + background: rgb(236, 237, 238); +} + +code { + padding: .1rem; +} + +pre code { + border: none; +} + +pre { + padding: 1rem; + overflow-x: auto; +} + +img { + max-width:100%; +} + +hr { + background: #000; + height: 1px; + border: 0; +} + +header { + flex-basis:10rem; + flex-grow:1; + position:relative; +} + +header a { + text-decoration: none; +} + +header li { + margin-bottom: .2rem; + text-align: right; + margin-right: 2rem; +} + +header a.active { + font-weight: bold; +} + +header,section { + padding:1rem; +} + +blockquote { + font-style: italic; + border-left: 5px solid #ececec; + padding-left: 1rem; +} + +h1,h2,h3,h4,h5 { + line-height: 1; + margin: 1rem 0; + font-weight: 600; +} + +section h1:first-child { + margin-top: 0; +} + +strong, b { + font-weight: bold; +} + +.photos ul { + list-style: none; +} + +.photos li { + margin-bottom: 1.5rem; +} + +.photo picture, .project picture { + margin-bottom: 0.5rem; +} + +.posts ul,header ul { + list-style:none; +} + +li { + list-style-position: inside; +} +.posts li { + align-items:center; + display:flex; + justify-content:space-between; + margin-bottom: .5rem; +} + +.posts li a, .posts li div, .projects li a { + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + text-decoration: none; +} + +.posts li time, .projects li time { + padding-left: 1rem; + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +.post ul, .project ul, .post ol { + //list-style-position: inside; +} + +main { + display:flex; + flex-wrap:wrap; + max-width:60rem; + margin:2rem auto; + padding:1rem; + background-color: transparent; +} + + +@media screen and (max-width: 45rem ) { +header li { + display: inline; + margin-right: 1rem; +} +.logo { + padding-bottom: 1rem; +} +header ul { + border-bottom: 3px solid #bcd1cf; + padding-bottom: 2rem; +} +nav ul { + border-right: 0px; +} + +.photos ul { + margin-top: 0.5rem; +} + +} + +section { + flex-basis:0; + flex-grow:999; + min-width:70%; + display:flex; + flex-direction:column; + +} + +figcaption { + font-size: smaller; +} + +@media print +{ + .no-print, .no-print * + { + display: none !important; + } +} diff --git a/assets/images/VGA/VGA_fig1.jpeg b/assets/images/VGA/VGA_fig1.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig10.jpeg b/assets/images/VGA/VGA_fig10.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig11.jpeg b/assets/images/VGA/VGA_fig11.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig12.jpeg b/assets/images/VGA/VGA_fig12.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig13.jpeg b/assets/images/VGA/VGA_fig13.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig14.jpeg b/assets/images/VGA/VGA_fig14.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig15.jpeg b/assets/images/VGA/VGA_fig15.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig16.jpeg b/assets/images/VGA/VGA_fig16.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig17.jpeg b/assets/images/VGA/VGA_fig17.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig18.jpeg b/assets/images/VGA/VGA_fig18.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig2.jpeg b/assets/images/VGA/VGA_fig2.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig3.jpeg b/assets/images/VGA/VGA_fig3.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig4.jpeg b/assets/images/VGA/VGA_fig4.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig5.jpeg b/assets/images/VGA/VGA_fig5.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig6.jpeg b/assets/images/VGA/VGA_fig6.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig7.jpeg b/assets/images/VGA/VGA_fig7.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig8.jpeg b/assets/images/VGA/VGA_fig8.jpeg Binary files differ. diff --git a/assets/images/VGA/VGA_fig9.jpeg b/assets/images/VGA/VGA_fig9.jpeg Binary files differ. diff --git a/assets/images/c-programming-language-chito.png b/assets/images/c-programming-language-chito.png Binary files differ. diff --git a/assets/js/search-script.min.js b/assets/js/search-script.min.js @@ -0,0 +1,6 @@ +/*! + * Simple-Jekyll-Search v1.7.2 (https://github.com/christian-fei/Simple-Jekyll-Search) + * Copyright 2015-2018, Christian Fei + * Licensed under the MIT License. + */ +!function(){"use strict";var f={load:function w(t,e){var n=function r(){return window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP")}();n.open("GET",t,!0),n.onreadystatechange=function i(e,n){return function(){if(4===e.readyState&&200===e.status)try{n(null,JSON.parse(e.responseText))}catch(t){n(t,null)}}}(n,e),n.send()}};(function y(t){if(!function e(t){return!!t&&"undefined"!=typeof t.required&&t.required instanceof Array}(t))throw new Error("-- OptionsValidator: required options missing");if(!(this instanceof y))return new y(t);var r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}});var n=function g(t,e){var n=e.length,r=t.length;if(n<r)return!1;if(r===n)return t===e;t:for(var i=0,o=0;i<r;i++){for(var u=t.charCodeAt(i);o<n;)if(e.charCodeAt(o++)===u)continue t;return!1}return!0},e=new function t(){this.matches=function(t,e){return n(e.toLowerCase(),t.toLowerCase())}};var r=new function O(){this.matches=function(e,t){return!!e&&(e=e.trim().toLowerCase(),(t=t.trim().toLowerCase()).split(" ").filter(function(t){return 0<=e.indexOf(t)}).length===t.split(" ").length)}};var l={put:function z(t){if(c(t))return s(t);if(function e(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function i(t){var e=[];a();for(var n=0,r=t.length;n<r;n++)c(t[n])&&e.push(s(t[n]));return e}(t);return undefined},clear:a,search:function S(t){return t?function a(t,e,n,r){for(var i=[],o=0;o<t.length&&i.length<r.limit;o++){var u=d(t[o],e,n,r);u&&i.push(u)}return i}(o,t,u.searchStrategy,u).sort(u.sort):[]},setOptions:function q(t){(u=t||{}).fuzzy=t.fuzzy||!1,u.limit=t.limit||10,u.searchStrategy=t.fuzzy?e:r,u.sort=t.sort||i}};function i(){return 0}var o=[],u={};function a(){return o.length=0,o}function c(t){return Boolean(t)&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return o.push(t),o}function d(t,e,n,r){for(var i in t)if(!p(t[i],r.exclude)&&n.matches(t[i],e))return t}function p(t,e){for(var n=!1,r=0,i=(e=e||[]).length;r<i;r++){var o=e[r];!n&&new RegExp(t).test(o)&&(n=!0)}return n}u.fuzzy=!1,u.limit=10,u.searchStrategy=u.fuzzy?e:r,u.sort=i;var h={compile:function j(r){return m.template.replace(m.pattern,function(t,e){var n=m.middleware(e,r[e],m.template);return void 0!==n?n:r[e]||t})},setOptions:function C(t){m.pattern=t.pattern||m.pattern,m.template=t.template||m.template,"function"==typeof t.middleware&&(m.middleware=t.middleware)}},m={};m.pattern=/\{(.*?)\}/g,m.template="",m.middleware=function(){};var v={merge:function L(t,e){var n={};for(var r in t)n[r]=t[r],"undefined"!=typeof e[r]&&(n[r]=e[r]);return n},isJSON:function M(t){try{return!!(t instanceof Object&&JSON.parse(JSON.stringify(t)))}catch(e){return!1}}};!function(t){var o={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'<li><a href="{url}" title="{description}">{title}</a></li>',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]},n=["searchInput","resultsContainer","json"],r=function y(e){if(!function n(t){return!!t&&"undefined"!=typeof t.required&&t.required instanceof Array}(e))throw new Error("-- OptionsValidator: required options missing");if(!(this instanceof y))return new y(e);var r=e.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}}({required:n});function i(t){o.success(t),l.put(t),function e(){o.searchInput.addEventListener("keyup",function(t){(function e(t){return-1===[13,16,20,37,38,39,40,91].indexOf(t)})(t.which)&&(u(),c(t.target.value))})}()}function u(){o.resultsContainer.innerHTML=""}function a(t){o.resultsContainer.innerHTML+=t}function c(t){(function e(t){return t&&0<t.length})(t)&&(u(),function i(t,e){var n=t.length;if(0===n)return a(o.noResultsText);for(var r=0;r<n;r++)t[r].query=e,a(h.compile(t[r]))}(l.search(t),t))}function s(t){throw new Error("SimpleJekyllSearch --- "+t)}t.SimpleJekyllSearch=function(t){return 0<r.validate(t).length&&s("You must specify the following required options: "+n),o=v.merge(o,t),h.setOptions({template:o.searchResultTemplate,middleware:o.templateMiddleware}),l.setOptions({fuzzy:o.fuzzy,limit:o.limit,sort:o.sortMiddleware}),v.isJSON(o.json)?i(o.json):function e(n){f.load(n,function(t,e){t&&s("failed to get JSON ("+n+")"),i(e)})}(o.json),{search:c}}}(window)}(); diff --git a/atom.xml b/atom.xml @@ -0,0 +1,28 @@ +--- +layout: null +--- + +<?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom"> + + <title>{{ site.title }}</title> + <link href="{{ site.url }}{{ site.baseurl }}/atom.xml" rel="self"/> + <link href="{{ site.url }}{{ site.baseurl }}/"/> + <updated>{{ site.time | date_to_xmlschema }}</updated> + <id>{{ site.url }}</id> + <author> + <name>{{ site.author.name }}</name> + <email>{{ site.author.email }}</email> + </author> + + {% for post in site.posts %} + <entry> + <title>{{ post.title | xml_escape }}</title> + <link href="{{ site.url }}{{ site.baseurl }}{{ post.url }}"/> + <updated>{{ post.date | date_to_xmlschema }}</updated> + <id>{{ site.url }}{{ post.id }}</id> + <content type="html">{{ post.content | xml_escape }}</content> + </entry> + {% endfor %} + +</feed> diff --git a/feed.json b/feed.json @@ -0,0 +1,60 @@ +--- +layout: null +--- +{ + "version": "https://jsonfeed.org/version/1", + "title": "{{ site.title | xml_escape }}", + "home_page_url": "{{ "/" | absolute_url }}", + "feed_url": "{{ "/feed.json" | absolute_url }}", + "description": {{ site.description | jsonify }}, + "icon": "{{ "/apple-touch-icon.png" | absolute_url }}", + "favicon": "{{ "/favicon.ico" | absolute_url }}", + "expired": false, + {% if site.author %} + "author": {% if site.author.name %} { + "name": "{{ site.author.name }}", + "url": {% if site.author.url %}"{{ site.author.url }}"{% else %}null{% endif %}, + "avatar": {% if site.author.avatar %}"{{ site.author.avatar }}"{% else %}null{% endif %} + },{% else %}"{{ site.author }}",{% endif %} + {% endif %} +"items": [ + {% for post in site.posts limit:36 %} + { + "id": "{{ post.url | absolute_url | sha1 }}", + "title": {{ post.title | jsonify }}, + "summary": {{ post.description | jsonify }}, + "content_text": {{ post.content | strip_html | strip_newlines | jsonify }}, + "content_html": {{ post.content | strip_newlines | jsonify }}, + "url": "{{ post.url | absolute_url }}", + {% if post.image.size > 1 %}"image": {{ post.image | jsonify }},{% endif %} + {% if post.link.size > 1 %}"external_url": "{{ post.link }}",{% endif %} + {% if post.banner.size > 1 %}"banner_image": "{{ post.banner }}",{% endif %} + {% if post.tags.size > 1 %}"tags": {{ post.tags | jsonify }},{% endif %} + {% if post.enclosure.size > 1 %}"attachments": [ { + "url": "{{ post.enclosure }}", + "mime_type": "{{ post.enclosure_type }}", + "size_in_bytes": "{{ post.enclosure_length }}" + },{% endif %} + "date_published": "{{ post.date | date_to_xmlschema }}", + "date_modified": "{{ post.date | date_to_xmlschema }}", + {% if post.author %} + "author": {% if post.author.name %} { + "name": "{{ post.author.name }}", + "url": {% if post.author.url %}"{{ post.author.url }}"{% else %}null{% endif %}, + "avatar": {% if post.author.avatar %}"{{ post.author.avatar }}"{% else %}null{% endif %} + } + {% else %}"{{ post.author }}"{% endif %} + {% else %} + "author": {% if site.author.name %} { + "name": "{{ site.author.name }}", + "url": {% if site.author.url %}"{{ site.author.url }}"{% else %}null{% endif %}, + "avatar": {% if site.author.avatar %}"{{ site.author.avatar }}"{% else %}null{% endif %} + } + {% else %} + "{{ site.author }}" + {% endif %} + {% endif %} + }{% if forloop.last == false %},{% endif %} + {% endfor %} + ] +} +\ No newline at end of file diff --git a/index.html b/index.html @@ -0,0 +1,15 @@ +--- +layout: default +--- + +<section class="posts"> +<ul> +{% for post in site.posts != 1 %} + <li {% cycle 'alternate-color': 'style="color: #799f7c;"', 'style="color: #b3b781;"'%} > + <a style="color: inherit" href="{{ site.baseurl }}{{ post.url }}">{{ post.title }}</a> + <time datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date: "%m-%d-%Y" }}</time> + </li> +{% endfor %} +</ul> +</section> + diff --git a/package-lock.json b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "n-channel", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "simple-jekyll-search": "^1.10.0" + } + }, + "node_modules/fuzzysearch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fuzzysearch/-/fuzzysearch-1.0.3.tgz", + "integrity": "sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==", + "license": "MIT" + }, + "node_modules/simple-jekyll-search": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/simple-jekyll-search/-/simple-jekyll-search-1.10.0.tgz", + "integrity": "sha512-4SdHfAjEe9mngvj4wt8A7OsF9Rl3+onHY1ruQC+bUnecbNbdvzVcAbL+UH5mE+v2CWgUb95dyZjHhyqUGSa2hA==", + "license": "MIT", + "dependencies": { + "fuzzysearch": "^1.0.3" + } + } + } +} diff --git a/package.json b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "simple-jekyll-search": "^1.10.0" + } +} diff --git a/search.js b/search.js @@ -0,0 +1,6 @@ +/*! + * Simple-Jekyll-Search v1.7.2 (https://github.com/christian-fei/Simple-Jekyll-Search) + * Copyright 2015-2018, Christian Fei + * Licensed under the MIT License. + */ +!function(){"use strict";var f={load:function w(t,e){var n=function r(){return window.XMLHttpRequest?new window.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP")}();n.open("GET",t,!0),n.onreadystatechange=function i(e,n){return function(){if(4===e.readyState&&200===e.status)try{n(null,JSON.parse(e.responseText))}catch(t){n(t,null)}}}(n,e),n.send()}};(function y(t){if(!function e(t){return!!t&&"undefined"!=typeof t.required&&t.required instanceof Array}(t))throw new Error("-- OptionsValidator: required options missing");if(!(this instanceof y))return new y(t);var r=t.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}});var n=function g(t,e){var n=e.length,r=t.length;if(n<r)return!1;if(r===n)return t===e;t:for(var i=0,o=0;i<r;i++){for(var u=t.charCodeAt(i);o<n;)if(e.charCodeAt(o++)===u)continue t;return!1}return!0},e=new function t(){this.matches=function(t,e){return n(e.toLowerCase(),t.toLowerCase())}};var r=new function O(){this.matches=function(e,t){return!!e&&(e=e.trim().toLowerCase(),(t=t.trim().toLowerCase()).split(" ").filter(function(t){return 0<=e.indexOf(t)}).length===t.split(" ").length)}};var l={put:function z(t){if(c(t))return s(t);if(function e(t){return Boolean(t)&&"[object Array]"===Object.prototype.toString.call(t)}(t))return function i(t){var e=[];a();for(var n=0,r=t.length;n<r;n++)c(t[n])&&e.push(s(t[n]));return e}(t);return undefined},clear:a,search:function S(t){return t?function a(t,e,n,r){for(var i=[],o=0;o<t.length&&i.length<r.limit;o++){var u=d(t[o],e,n,r);u&&i.push(u)}return i}(o,t,u.searchStrategy,u).sort(u.sort):[]},setOptions:function q(t){(u=t||{}).fuzzy=t.fuzzy||!1,u.limit=t.limit||10,u.searchStrategy=t.fuzzy?e:r,u.sort=t.sort||i}};function i(){return 0}var o=[],u={};function a(){return o.length=0,o}function c(t){return Boolean(t)&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return o.push(t),o}function d(t,e,n,r){for(var i in t)if(!p(t[i],r.exclude)&&n.matches(t[i],e))return t}function p(t,e){for(var n=!1,r=0,i=(e=e||[]).length;r<i;r++){var o=e[r];!n&&new RegExp(t).test(o)&&(n=!0)}return n}u.fuzzy=!1,u.limit=10,u.searchStrategy=u.fuzzy?e:r,u.sort=i;var h={compile:function j(r){return m.template.replace(m.pattern,function(t,e){var n=m.middleware(e,r[e],m.template);return void 0!==n?n:r[e]||t})},setOptions:function C(t){m.pattern=t.pattern||m.pattern,m.template=t.template||m.template,"function"==typeof t.middleware&&(m.middleware=t.middleware)}},m={};m.pattern=/\{(.*?)\}/g,m.template="",m.middleware=function(){};var v={merge:function L(t,e){var n={};for(var r in t)n[r]=t[r],"undefined"!=typeof e[r]&&(n[r]=e[r]);return n},isJSON:function M(t){try{return!!(t instanceof Object&&JSON.parse(JSON.stringify(t)))}catch(e){return!1}}};!function(t){var o={searchInput:null,resultsContainer:null,json:[],success:Function.prototype,searchResultTemplate:'<li><a href="{url}" title="{description}">{title}</a></li>',templateMiddleware:Function.prototype,sortMiddleware:function(){return 0},noResultsText:"No results found",limit:10,fuzzy:!1,exclude:[]},n=["searchInput","resultsContainer","json"],r=function y(e){if(!function n(t){return!!t&&"undefined"!=typeof t.required&&t.required instanceof Array}(e))throw new Error("-- OptionsValidator: required options missing");if(!(this instanceof y))return new y(e);var r=e.required;this.getRequiredOptions=function(){return r},this.validate=function(e){var n=[];return r.forEach(function(t){"undefined"==typeof e[t]&&n.push(t)}),n}}({required:n});function i(t){o.success(t),l.put(t),function e(){o.searchInput.addEventListener("keyup",function(t){(function e(t){return-1===[13,16,20,37,38,39,40,91].indexOf(t)})(t.which)&&(u(),c(t.target.value))})}()}function u(){o.resultsContainer.innerHTML=""}function a(t){o.resultsContainer.innerHTML+=t}function c(t){(function e(t){return t&&0<t.length})(t)&&(u(),function i(t,e){var n=t.length;if(0===n)return a(o.noResultsText);for(var r=0;r<n;r++)t[r].query=e,a(h.compile(t[r]))}(l.search(t),t))}function s(t){throw new Error("SimpleJekyllSearch --- "+t)}t.SimpleJekyllSearch=function(t){return 0<r.validate(t).length&&s("You must specify the following required options: "+n),o=v.merge(o,t),h.setOptions({template:o.searchResultTemplate,middleware:o.templateMiddleware}),l.setOptions({fuzzy:o.fuzzy,limit:o.limit,sort:o.sortMiddleware}),v.isJSON(o.json)?i(o.json):function e(n){f.load(n,function(t,e){t&&s("failed to get JSON ("+n+")"),i(e)})}(o.json),{search:c}}}(window)}(); diff --git a/search.json b/search.json @@ -0,0 +1,17 @@ +--- +layout: null +--- +[ + {% for post in site.posts %} + { + + "title" : "{{ post.title | strip_html | escape }}", + "description" : "{{ post.description | escape }}", + "url" : "{{ site.baseurl }}{{ post.url }}", + "category" : "{{post.categories | join: ', '}}", + "tags" : "{{ post.tags | join: ', ' }}", + "date" : "{{ post.date }}" + + } {% unless forloop.last %},{% endunless %} + {% endfor %} +]