2015年10月10日 星期六

Rust 的 C 語言界面:struct 和 enum

對於打算使用新語言的人來說,能否容易地使用 C 語言大量的函式庫是選擇語言的重要指標。

使用 C 的函式庫,簡單分兩個層次:
  1. 呼叫 C 的函式,以及提供 callback
  2. 使用 C 的資料結構。
過往的經驗告訴我,麻煩在第二點。不同語言的資料結構採用不同的記憶體 layout,因此很難直接使用他種語言的資料結構。於是常要先撰寫一堆存取資料結構的小函式,再透過這些函式來使用資料結構。

這假日我好好研究了這個問題,驚奇發現 Rust 是僅次於 C++,我用過最容易和 C 界接的語言。以下以我最近處理的一個 struct 裡面有 struct,還有 enum,更有陣列的資料結構的簡化版,說明如何做出有同樣 layout 的 Rust 資料結構。

#define EC_MAX_STRING_LENGTH 64
#define EC_MAX_PORTS 4

typedef enum {
    EC_PORT_NOT_IMPLEMENTED, 
    EC_PORT_NOT_CONFIGURED, 
    EC_PORT_EBUS, 
    EC_PORT_MII 
} ec_slave_port_desc_t;

typedef struct {
    uint8_t link_up; 
    uint8_t loop_closed; 
    uint8_t signal_detected; 
} ec_slave_port_link_t;

typedef struct {
    uint16_t position; 
    uint32_t vendor_id; 
    uint32_t product_code; 
    struct {
        ec_slave_port_desc_t desc; 
        ec_slave_port_link_t link; 
        uint32_t receive_time; 
        uint16_t next_slave; 
    } ports[EC_MAX_PORTS]; 
    uint8_t error_flag; 
    char name[EC_MAX_STRING_LENGTH]; 
} ec_slave_info_t;

以下使用 Rust 做出在記憶體 layout 一模一樣的資料結構:

// 首先要使用 libc 中提供的 C 的型別。
// int  對應 c_int
// uint 對應 c_uint
// char 對應 u8
// int16_t, uint16_t 等直接對應 int16_t 及 uint16_t。

extern crate libc;
use libc::{c_uint, c_int, int16_t, uint8_t, uint16_t, uint32_t};

const EC_MAX_PORTS: usize = 4;
const EC_MAX_STRING_LENGTH: usize = 64;

// 在 enum 前加上 #[repr(C)],告知 Rust 採用 C 的 layout。

#[repr(C)]
pub enum ec_slave_port_desc_t {
    EC_PORT_NOT_IMPLEMENTED = 0,
    EC_PORT_NOT_CONFIGURED,
    EC_PORT_EBUS,
    EC_PORT_MII,
}

#[repr(C)]
pub struct ec_slave_port_link_t {
    link_up: uint8_t,
    loop_closed: uint8_t,
    signal_detected: uint8_t,
}

// 對於 C 結構中的陣列,就使用 Rust 的陣列取代。
#[repr(C)]
pub struct ec_slave_info_t {
    pub position: uint16_t,
    pub vendor_id: uint32_t,
    pub product_code: uint32_t,
    ports: [ec_slave_port_t; EC_MAX_PORTS],
    al_state: uint8_t,
    error_flag: uint8_t,
    name: [u8;EC_MAX_STRING_LENGTH],
}

我產生上列程式的組合語言列表,確定它和 C 語言的記憶體配置一模一樣。 比較麻煩的是給預設值初始化時,這部份以後有機會再說明。

2015年1月31日 星期六

BotBone: Reactive image with D3.js

假日中為我們的 BotBone 計畫設計一款互動式界面。我先以手機拍照,以 Inkscape 將照片內嵌到 SVG 檔中。再以 D3.js 加上互動元件。

初步成果如下圖:
這工作耗費我兩天的時間,接下來就要讀取 BotBone 的 I/O,並依據實體 I/O 的狀態,動態地改變圖片上的互動元件。

未來希望能將這類工作簡化。最好一小時就能完成。

2015年1月5日 星期一

Debian 下的 PostgreSQL

開始建公司網頁。決定採用 Yahoo 的 Isomorphic React 作為前後端的架構。資料庫則使用剛出來的 PostgreSQL 9.4。

我使用 Debian 7,閱讀 /usr/share/doc/postgresql-9.4/README.Debian.gz 讓我很快能建立一個 role 及一個 database。但是建立出來的 role 沒有自己的 password。因此還是乖乖地閱讀 PostgreSQL 文件,並上網查資料。以下是簡單的報告。

首先先依 README.Debian 建立 role 和 database:
$sudo -u postgres bash
$createuser -DRS joe
$createdb -O joe joework
當執行 psql 時,
$psql -U joe -W joework
因為沒有 joe 的 password,所以無法進入。 因為 createuser 時未給 password。
重新來過:
$dropdb joework
$dropuser joe
$createuser -DPRS joe
$createdb -O joe joework
當執行 psql 時,
$psql -U joe -W joework
psql: FATAL:  Peer authentication failed for user "joe"
修改 /etc/postgresql/9.4/main/pg_hba.conf 中的 Authentication method:
# "local" is for Unix domain socket connections only
#local   all             all                                     peer
local   all             all                                     md5
重新啟動 postgresql,因為我未使用 systemd,因此執行:
$sudo service postgresql restart
再次執行 psql,
$psql -U joe -W joework
成功。

2014年12月29日 星期一

J1 Forth CPU 研究之五:算術邏輯單元(ALU)

這是一系列文章中的第五篇,這系列的文章是要解讀 J1 CPU 的設計以及在其上的 Forth 語言的使用法。

這篇談的是 j1 CPU 的 Verilog 程式中的算術邏輯單元(ALU),請見以下檔案:

src/hardware/verilog/j1.v


為了方便說明,以下程式的次序不一定和 j1.v 中一致。

  wire [15:0] insn;
  wire [15:0] immediate = { 1'b0, insn[14:0] };
  wire is_alu = (insn[15:13] == 3'b011);
  wire is_lit = (insn[15]);


  reg [3:0] st0sel;
  always @*
  begin
    case (insn[14:13])
      2'b00: st0sel = 0;          // ubranch
      2'b01: st0sel = 1;          // 0branch
      2'b10: st0sel = 0;          // call
      2'b11: st0sel = insn[11:8]; // ALU
      default: st0sel = 4'bxxxx;
    endcase
  end



  always @*
  begin
    if (insn[15])
      _st0 = immediate;
    else
      case (st0sel)
        4'b0000: _st0 = st0;
        4'b0001: _st0 = st1;
        4'b0010: _st0 = st0 + st1;
        4'b0011: _st0 = st0 & st1;
        4'b0100: _st0 = st0 | st1;
        4'b0101: _st0 = st0 ^ st1;
        4'b0110: _st0 = ~st0;
        4'b0111: _st0 = {16{(st1 == st0)}};
        4'b1000: _st0 = {16{($signed(st1) < $signed(st0))}};
        4'b1001: _st0 = st1 >> st0[3:0];
        4'b1010: _st0 = st0 - 1;
        4'b1011: _st0 = rst0;
        4'b1100: _st0 = |st0[15:14] ? io_din : ramrd;
        4'b1101: _st0 = st1 << st0[3:0];
        4'b1110: _st0 = {rsp, 3'b000, dsp};
        4'b1111: _st0 = {16{(st1 < st0)}};
        default: _st0 = 16'hxxxx;
      endcase
  end

以上總共 40 行,加上之前的 90 行,我們已經看懂了 j1.v 的 130/200。接下來的兩篇處理RAM 及 memory mapped I/O,我們對 j1.v 的探索就算完成。

2014年4月1日 星期二

Mapabone 硬體設計之一

目前正在設計一款針對創客社群,類似 Raspberry Pi 和 Beaglebone,但是更強調 realtime 性能以及運動控制的軟硬體平台。這個軟硬體平台暫時命名為 Mapabone。目前的硬體設計原則如下:

  1. 參考 Beaglebone 的設計,但針對其複雜不便之處加以簡化。
  2. 擁抱 Raspberry Pi 的 pin definition 以擁抱 Raspberry Pi 社群。
  3. 擴充更多的 I/O 腳位以方便設計週邊。
  4. 內建 FPGA 以實現 MPU 無法達到的性能。
此外還進行其他的修正,以附合我心中好用的運動控制硬體平台的形象。

I'm designing for makers a software/hardware platform, which likes Raspberry Pi or Beaglebone, but emphasize realtime performance and motion control. I named this software/hardware platform Mapabone temporarily. My design guidelines are:

  1. Adapt the design of Beaglebone, but simplify its complexity.
  2. Embrace the P1 pin definition of Raspberry Pi, to embrace the Raspberry Pi community.
  3. Add more connectors to facilitate the development of peripherials.
  4. Embed an FPGA to provide performance which can not be achieved with MPU.
Many other improvements are underway in order to realize the ideal motion control platform in my mind.

進行這設計已有一段時間。已完成腳位的分析,開始進行線路的修改。但因為我從未使用過 OrCAD,在此一系列「Mapabone 硬體設計筆記」中記錄我在修改線路時的各種經驗,以及對這硬體平台的想法。

第零課:New project 和 New design

首先學習建立一個新的 Project 和 Schematics。選擇 File->New->Project...
輸入 project 名稱及位置,並選擇要進行的設計是 Schematic。
這樣就可以開始了!

第一課:Place Part

就是把零件放在設計圖面上,再用線連起來。我學會了置放以下的零件:電阻(R)、電容(C)、電感(L)、電源(V)。作法很簡單,選擇 Place->Part,再選擇零件。比較難的是要知道如何找到這些零件。最快的方法是輸入零件的第一個字母再在清單中尋找。以下是我們用的元件。

  • R/ANALOG
  • C/ANALOG
  • L/ANALOG
  • VPULSE/SOURCE

再來是接線和地線,分別選擇 Place->Wire 和 Place->Ground。

以下是我的完成圖:



2013年10月12日 星期六

學習筆記:Debian on Beaglebone

首先參考 Compile Linux Kernel 3.2 for Arm and Emulate with QEMU 。
這篇文章中不討論 u-boot, 因此編譯的核心是 zImage 而非 uImage。只利用 initramfs 執行一個很小的 init 程式。

安裝 Emdebian Toolchain 。

再來參考 How to Cross Compile Arm Kernel under Ubuntu-10-10 。
這篇講如何編譯 Ubuntu,但是同樣的作法用在 Debian 不成功。末尾說明了如何編從 kernel.org 拿到的核心,到是可以用的。只是沒說明編了 uImage 後要如何和 u-boot 結合。

之後參考 Installing Debian On TI BeagleBone 。
這篇使用 linaro-image-tools 和 live-build,以及 qemubuilder 。
依照文中的方法建造 Debian binary rootfs 不成功。
之後再試建造 u-boot,但之前使用 qemubuilder 失敗過,得到以下訊息,
   FATAL: kernel too old
據說是因 c 函式庫的新舊問題造成,我沒時間思考如何解決,因此先放棄。
建造 kernel 也使用 qemubuilder,因此放棄。
再來是 hwpack。這部份不熟,先擱置。

再參考 BeagleBone Debian Wheezy snapshot armhf based RootFileSystem 。
先取得 cross-compilers:
   $ wget -c https://launchpad.net/linaro-toolchain-binaries/trunk/2013.07/+download/gcc-linaro-arm-linux-gnueabihf-4.8-2013.07-1_linux.tar.xz
   $ tar xJf gcc-linaro-arm-linux-gnueabihf-4.8-2013.07-1_linux.tar.xz
   $ export CC=`pwd`/gcc-linaro-arm-linux-gnueabihf-4.8-2013.07-1_linux/bin/arm-linux-gnueabihf-
確定是 32bit 版本:
   $ {CC}gcc --version
取得最新版的 u-boot:
   $ git clone git://git.denx.de/u-boot.git
   $ cd u-boot/
   $ git checkout v2013.07 -b tmp
patch 後進行編譯:
   $ wget -c https://raw.github.com/eewiki/u-boot-patches/master/v2013.07/0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch
   $ patch -p1 < 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch
   $ make ARCH=arm CROSS_COMPILE=${CC} distclean
   $ make ARCH=arm CROSS_COMPILE=${CC} am335x_evm_config
   $ make ARCH=arm CROSS_COMPILE=${CC}
接下來編譯 kernel,在此不使用作者提供的 git.sh, 因曾無法 git clone 或 fetch from kernel.org 而出問題。而且未來將針對特定的 branch 進行 patch。作法如下:

。不使用文中所用的 git.sh,因此先 clone 之後,修改 system.sh 中的 LINUX_GIT,並且將 scripts/git.sh 中所有從 kernel.org 取得 repository 的程式片段都放在註解中。之後就可以成功編譯。
可惜的是我在成功產生一個  3.2 的 MicroSD 後,以此 MicroSD boot linux,在 boot kernel  階段失敗。當掉的原因是因為我實際上產生的是 3.12 的 kernel 而非 3.2 的 kernel。
在 linux-dev 中執行
   git checkout origin/am33x-v3.2 -b tmp
並在 linux-src 中執行
    git checkout v3.2
後,成功。

再參考 Flashing Ubuntu 13-04 or debian wheezy to the beaglebone black emmc 。

2013年9月4日 星期三

YUI 的 Promise

YUI 的 Promise 用法如下:

var promise = new Y.Promise(function (resolve, reject) {
   // 非同步的呼叫, 當非同步的呼叫完成後,
   // 依結果執行 resolve 或是 reject。
}

在內部的實作中,我們可見 Y.Promise 定義了一個 Y.Promise.Resolver,再使用這個 resolver
來執行以上傳給 Y.Promise 的 function。

fn.call (this, function(value) {
      resolver.fulfill(value);
   }, function (value) {
      resolver.reject(value);
   }
}):

因此可知 resolve(value) 就是執行 resolver.fulfill(value), reject(value) 就是執行 resolver.reject(value)。

所以可把對 Y.Promise 的說明改成如下:

var promise = new Y.Promise(function (resolve, reject) {
   // 非同步的呼叫, 當非同步的呼叫完成後,
   // 依結果執行 resolver.fulfill 或是 resolver.reject。
}

由於看 YUI promise 以上程式時有點吃力,因此將理解記錄於此。

2013年2月2日 星期六

Firefox 18 vs. Chrome 24

我們的產品以網頁為人機界面。這些日子我最關心的事情之一就是 Firefox 在 javascript performance 上的性能提升。雖然 Chrome 的性能已經滿足產品的需求,我仍希望 Chrome 不是唯一的選擇。畢竟,Firefox 在客製化的能力上略勝一籌。

Firefox 18 採用 IonMonkey 為引擎,首次使得我們的產品能在 Firefox 上使用。雖然切換畫面時,仍有延遲,已是很大的進步。

Google 在去年八月提出新的 javascript 速度評估的基準 octane。經瞭解,他的基準包括了以下幾項新的測試:
  • Box2DWeb
  • Mandreel
  • Pdf.js
  • GB emulator
  • CodeLoad
其中的 Box2DWeb 測試 2D 繪圖能力。Mandreel 測試 3D 繪圖能力。這兩項對我未來的產品都很重要。

Octane 的連結位於:
http://octane-benchmark.googlecode.com/svn/latest/index.html
只要點選以上連結,就可以進行測試。

以下是使用我的筆電針對 octane v1 測試的結果:
測試測試內容Chrome 24Firefox 18
RichardsCore language features104748991
DeltablueCore language features1381211333
CryptoBit and math operations114859111
RaytraceCore language features146826701
EarlyBoyerMemory and GC2367912388
RegexpStrings and Arrays3061922
SplayMemory and GC435510431
NavierStokesStrings and Arrays1501715418
pdf.jsStrings and Arrays102373913
MandreelVirtual machine101546803
GB emulatorVirtual machine111116212
CodeLoadLoading and Parsing104448634
Box2DWebBit and math operations116677325

可以看出 Firefox 18 和 Chrome 24 仍有很大差距。

不過,Mozilla 的團隊每日比較 Firefox 和 Chrome 的性能(http://arewefastyet.com/), 從 2012 年十二月到 2013 年二月初,可以見到 Firefox 一路迫進 Chrome 的性能。該說唉呀已經達到  Chrome 的 90% 的速度了。想來,我的產品上切換畫面的延遲可能已得到解決,只是還得等到 Firefox 19,20。但是,是不是有可能更好,進而和 Chrome 平手?

似乎是可能的。Firefox 的 Generational GC 尚未完成。它的完成將帶來 Firefox 性能的再次提升。

2013年1月11日 星期五

WebM 初探

最近研究 WebM,尤其是其 encoder 的部份。以下說明我研究的結果:

Firefox 和 Google Chrome 都使用 WebM project 的 libvpx 函式庫來處理 WebM。WebM 包括了 video 和 audio 兩個部份,video 部份使用 VP8 video codec,audio 部份使用 Vorbis audio codec。這兩個部份被放在 container 內。Container 的設計參考了 Matroska container。

libvpx 內的 simple encoder 範例讀取了 YV12 檔案,編碼成 IVF 檔案。

YV12 是 YUV 的一種。YUV 是編譯 true color 顏色空間的種類。YUV 發明於黑白電視和彩色電視的過渡期,為了訊號能和黑白電視相容,不採用 RGB 的編碼,而採用 YUV,Y 代表明亮度,也就是給黑白電視的訊號。UV 則是色度、濃度。

IVF (Indeo Video Format) 是 Intel 開發的格式,在 1990 年代很流行,後被 MPEG 標準打敗。IVF  在 WebM 計劃中被用來傳送 raw VP8 data。

2011年11月30日 星期三

C 語言上的 Forth

三十年以上功力,我也算是 Forth 的老手了。開發了一套自己的 Forth,雖然不用 C,但它的確是建立高階語言上的。

很多人反對 C 語言上的 Forth。我想依據我的經驗,說明一下 C 語言上的 Forth 的好處,讓大家思考一下:

  1. 當您的大型專案是以 C 語言寫成,當這專案編譯成執行檔後您要如何測試這個專案的各個小零件?

反對 C 語言上的 Forth 的人會選擇 ruby, python, javascript, lua, scheme 或其他能和 C 語言連結的 scripting languages 來和這專案連結,以 scripting language 和執行檔內的副程式互動,進行測試。

贊成 C 語言上的 Forth 的人會自建 Forth,或是用別人建好的 C 語言上的 Forth,作為這專案的互動式測試工具。

 2. 如果您有兩個執行檔,他們必須溝通並且呼叫對方的某些函式。一個執行檔以 Java 寫成,另一個是以 C 寫成。請問您要如何處理這樣的問題?

反對 C 語言上的 Forth 的人可能會在 C 語言的專案中內建 lua,因為聽說 lua 是最適合內建在 C 程式中的 scripting language 了。但是 Lua 不能內建在 Java 中,所以只好在 Java 的專案內選擇 JRuby。

贊成 C 語言上的 Forth 的人會在 Java 上建一套 Forth,畢竟那是很容易的事。更偷懶的會使用別人在 Java 上建好的 Forth,配合他在 C 語言那端的 Forth。然後,用同一種語言來解決問題。

有句話,一招半式闖江湖,會上 Forth 的一招半式,可以闖闖 C 語言的江湖、Java 語言的江湖,還有 MCU 江湖。

我希望用我的經驗讓反對 C 語言上的 Forth 的 Forth 愛好者,能瞭解為什麼會有 C 語言上的 Forth。C 語言上的 Forth 不會因為別人反對而消失,因為它們以簡單的方法解決了現實中程式開發的問題,只有真正面對過這些問題的人才瞭解到它的價值。

2011年10月7日 星期五

J1 Forth CPU 研究之四:資料堆疊和返回堆疊

這是一系列文章中的第四篇,這系列的文章是要解讀 J1 CPU 的設計以及在其上的 Forth 語言的使用法。

這篇談的是 j1 CPU 的 Verilog 程式中和資料堆疊 (Data Stack) 和返回堆疊 (Return Stack) 有關的部份,請見以下檔案:

src/hardware/verilog/j1.v

一般的 CPU 在設計上只有一個堆疊,這個堆疊在副程式呼叫時有三個功能:
  1. 存放副程式的的參數 (parameters)。
  2. 存放副程式的返回位址。
  3. 存放區域變數 (local variables)。
當副程式有回傳值時,這值會被放在一個特定的暫存器中,大多數的程式語言的副程式最多只有一個回傳值,理由在此。

Forth CPU 和的一般 CPU 在設計上有個不同之處: Forth CPU 有兩個堆疊,回傳的資料不放暫存器,而是放在堆疊上。因此可以回傳多值。這兩個堆疊的名稱及功能是:
  1. 資料堆疊 (Data Stack):存放副程式的的參數 (parameters) 以及副程式產生的資料。
  2. 返回堆疊 (Return Stack): 存放副程式的返回位址。有時會在副程式計算過程中用來暫時保存資料堆疊上的資料。
某些 Forth 會使用第三個堆疊處理區域變數,但是許多 Forth 語言的愛好者摒棄區域變數這樣的概念。J1 Forth CPU 在設計上只有兩個堆疊。

資料堆疊最上面的資料常被稱為 T (Top),在其下的資料常被稱為 N (Next)。由於 ALU 的計算大量使用 T 和 N,許多 Forth CPU 或是 Forth virtual machine 的設計中,T 會被設計成暫存器,以加快 ALU 的計算速度。當要使用 T 時,直接讀取對應的暫存器,當要使用 N 時,讀取資料堆疊的堆疊指標指向的堆疊頂端。這使得 T 和 N 可以同時被讀取。在這樣的設計下,雖然 T 是寫 Forth 程式時的資料堆疊頂,但是以 Verilog 實作時,N 才是實作的資料堆疊的疊頂。因此要實現 Forth 程式中將數值資料推上資料堆疊的行為時,除了寫資料到 T 中,還必須把 T 中的資料推上堆疊(也就是機器碼編譯中的 T->N)。因為 Verilog 平行處理的特性,這兩個行為可以同時發生。

要注意,和資料堆疊不同,返回堆疊最上面的資料並未放在一個獨立的暫存器中,這使得將數值推上資料堆疊與推上返回堆疊的程式略有不同。以下是 Verilog 的宣告:

  reg [4:0] dsp;  // 資料堆疊指標,指向 N
  reg [4:0] _dsp;  // 下一時刻的資料堆疊指標
  reg [15:0] st0;  // T,存放 Forth 資料堆疊最頂端資料的暫存器
  reg [15:0] _st0; // 下一時刻的 T
  wire [15:0] st1; // N,Forth 資料堆疊中在 T 之下的資料,Verilog 中資料堆疊的疊頂。請參考以下程式中的 assign 敍述。
  wire _dstkW;     // D stack write

  reg [4:0] rsp; // 返回堆疊指標
  reg [4:0] _rsp; // 下一時刻的返回堆疊指標
  wire [15:0] rst0; // 返回堆疊的疊頂,請見以下程式中的 assign 敍述。
  reg _rstkW;     // R stack write
  reg [15:0] _rstkD; // 要寫入返回堆疊的資料

  reg [15:0] dstack[0:31]; // 資料堆疊,深度為 32,加上 st0,深度為 33
  reg [15:0] rstack[0:31]; // 返回堆疊,深度為 32

以下程式處理寫入堆疊的動作。在 sys_clk_i 的上升緣,如果資料堆疊寫入訊號 _dstkW 為真,則執行 T->N 的行為,把 st0 的資料寫入 dstack[_dsp]。_dsp 為下一時刻的資料堆疊指標。所以是未來的 N 的位置。同樣的,如果返回堆疊寫入訊號 _rstkW 為真,則把 _rstkD 的內容寫到返回堆疊中 _rsp 指到的位置。_rsp 為下一時刻的返回堆疊指標。
  always @(posedge sys_clk_i)  begin
    if (_dstkW)
      dstack[_dsp] = st0;
    if (_rstkW)
      rstack[_rsp] = _rstkD;
  end
  assign st1 = dstack[dsp];
  assign rst0 = rstack[rsp];

以下程式決定寫入訊號 _dstkW、_rstkW,及下一刻堆疊指標 _dsp, _rsp 的值。請參考機器碼的編碼:

其中,要寫入資料堆疊的時機(_diskW為真)是當機器碼最高位元為 1,也就是 literal 時,或是機器碼為 ALU,且第 7 位元為 1 (也就是 T->N) 時。此時都要做 T->N 的動作。

  assign _dstkW = is_lit | (is_alu & insn[7]);

以下資料決定執行 ALU 後資料堆疊和返回堆疊指標增減的量。
  wire [1:0] dd = insn[1:0];  // D stack delta
  wire [1:0] rd = insn[3:2];  // R stack delta

  always @*
  begin
    if (is_lit) begin                       // 如果是 literal,數值資料
      _dsp = dsp + 1;                  // 此時因要推資料上資料堆疊,資料堆疊指標加 1
      _rsp = rsp;                          // 返回堆疊指標不變。
      _rstkW = 0;                         // 不寫入返回堆疊
      _rstkD = _pc;                      // 因為不寫入,這行其實沒有作用。
    end else if (is_alu) begin        // 如果是 ALU 運算
      _dsp = dsp + {dd[1], dd[1], dd[1], dd};   // 依 dd 增減資料堆疊指標
      _rsp = rsp + {rd[1], rd[1], rd[1], rd};        // 依 rd 增減返回堆疊指標
      _rstkW = insn[6];                                  // 由 T->R 欄位決定要不要寫入返回堆疊
      _rstkD = st0;                                        // 如果要寫入,被寫入的資料是 T
    end else begin                      // 如果是 jump/call
      // predicated jump is like DROP
      if (insn[15:13] == 3'b001) begin    // 如果是 conditional jump,拋棄堆疊上用來做判斷的資料,因此堆疊指標減一。
        _dsp = dsp - 1;
      end else begin                              // 如果只是單純的 jump,維持原來的堆疊指標。
        _dsp = dsp;
      end
      if (insn[15:13] == 3'b010) begin    // 如果是 call,呼叫副程式
        _rsp = rsp + 1;                            // 此時把 program counter + 1 堆上返回堆疊。
        _rstkW = 1;                                 // 因此要寫入返回堆疊。
        _rstkD = {pc_plus_1[14:0], 1'b0}; // 實際上推入的值必須乘以二,轉成 byte 位址。
                                                         // 轉成 byte 位址的理由在未來討論 Xilinx 的內部 RAM 時說明。
      end else begin                    // 當以上皆非
        _rsp = rsp;                        // 不改變返回堆疊
        _rstkW = 0;
        _rstkD = _pc;
      end
    end
  end

以上總共 60 行,加上第三篇的 30 行,我們已經看懂了 j1.v 的 90/200,接近一半的程式了。

2011年10月3日 星期一

J1 Forth CPU 研究之三:系統重設和 program counter

這是一系列文章中的第三篇,這系列的文章是要解讀 J1 CPU 的設計以及在其上的 Forth 語言的使用法。

這篇要談的是 j1 CPU 的 Verilog 程式中和系統重設以及 program counter 有關的部份,請見以下檔案:

src/hardware/verilog/j1.v

在 j1.v 中,系統重設的訊號是 sys_rst_i,因此搜尋所有 sys_rst_i 出現的地方。
以下是其中一處:

  always @(posedge sys_clk_i)
  begin
    if (sys_rst_i) begin
      pc <= 0;
      dsp <= 0;
      st0 <= 0;
      rsp <= 0;
    end else begin
      dsp <= _dsp;
      pc <= _pc;
      st0 <= _st0;
      rsp <= _rsp;
    end
  end

說明如下:
  1. sys_clk_i:系統的 clock 訊號
  2. always @(poseedge sys_clk_i):在系統的 clock 訊號的上升邊緣總是要執行底下 begin 至 end 的事。
  3. sys_rst_i:系統的重設訊號
  4. pc:program counter
  5. dsp: 資料堆疊指標
  6. st0: 資料堆疊頂端的暫存器
  7. rsp:返回堆疊的指標
因此,這段程式是說,在系統的 clock 訊號的上升邊緣如果重設訊號為真,就把 pc, dsp, st0, rsp 都設為零。否則, pc, dsp, st0 和 rsp 會被設為 _pc, _dsp, _st0, _rsp。其中 _pc, _dsp, _st0, _rsp 這幾個值會在別處計算。
因此我們知道,這個 CPU 一被重設,就會從位址零開始執行。而且,所有的堆疊都被清空。

以下是另一處:

  always @*
  begin
    if (sys_rst_i)
      _pc = pc;
    else
      if ((insn[15:13] == 3'b000) |
          ((insn[15:13] == 3'b001) & (|st0 == 0)) |
          (insn[15:13] == 3'b010))
        _pc = insn[12:0];
      else if (is_alu & insn[12])
        _pc = rst0[15:1];                          // 注意推上返回堆疊的副程式返回位址是 8 bit 的 byte 位址,而 program counter 使用的是 16 bit 的 word 位址,因此用 rts0[15:1],不使用 rts0[15:0]。
      else
        _pc = pc_plus_1;
  end

為了瞭解以上程式必須瞭解 J1 機器碼的編碼,請見下圖或是 J1: a small Forth CPU Core for FPGAs。

程式說明如下:
  1. always @*:總是要做以下 begin 至 end 間的事。
  2. insn:放的是目前要執行的機器碼。
  3. 如果系統重設訊號 sys_rst_i 為真,則 _pc = pc, 不做任何改變。
  4. 如果重設訊號為假,則檢查目前要執行的機器碼,
    1. 如果第 13 到第 15 位元為二進制的 000, 或是 001 或是 010,分別是 j1 CPU  的 jump, conditaional jump 及 call 指令,則 program counter 被設為機器碼中 0 到 12 位元的值。
    2. 如果機器碼是 alu (13到15位元為二進制的 011),而且 12 位元為 1,則這是一個把返回堆疊頂的資料放到 program counter 的指令,就是副程式返回,就是 Forth 的 NEXT 指令啦。
    3. 如果以上皆非,那麼 program counter 等於 program counter 加一,處理下一個機器碼。
程式中的 3'b000 是 Verilog 的語法,一個 3bit 的二進制數字 000。3'b001 及 3'b010 依此類推。


程式中 |st0 == 0 的 | 會把 st0 中的 bits 都 or 起來,因此,這句判斷堆疊最上面的資料是否為零。這被用在 conditional jump:如果堆疊最上面的資料為零就要 jump。


由於 jump, conditional jump 及 call 後的位址只有 0 到 12 共 13 個位元,我們知道 j1 的程式最大只有 8K。不是 8K bytes,因為 J1 是 16 位元的 CPU,每個機器碼長度為 16 bits。

這樣子,我們就瞭解了 j1 在系統重設時的行為,以及它是如何實現 jump, conditional jump, call 及 ret (NEXT) 的。 由於 j1.v 只有 200 行程式,我們已經看懂了它的 30/200 之一的程式。

不過,在看上面的程式時要注意 Verilog 的 <= 和 = 的差異,這部份比較隱晦,請閱讀相關書籍。

2011年10月2日 星期日

J1 Forth CPU 研究之二:如何以 Xilinx ISE 來 Synthesize J1 CPU


這是我有關 J1 Forth CPU 的系列文章中的第二篇,雖然,這篇其實是寫於第一篇之前,已經在符式協會論壇發表過。

以下是當初發表的內容:
因未來工作需要,決定建立 J1 Forth CPU 和 Ethernet 結合的技術。第一個步驟,就是將 WGE100 Camera 的 Firmware 以 Xilinx 的 SP601 板跑起來。

但是在改為 SP601 前,由於 WGE100 Camera 使用的是 Spartan 3E,我先以 Spartan 3E 進行 FPGA 的 Synthesis。以下說明我的經驗:


之後,以 Xilinx ISE 開了一個新的 project,選擇 Family Spartan3E,Device XC3S500E,Package CP132,這應該就是原來的作者使用的 Device 和 Package。


然後把以下的檔案加進來:

verilog 目錄下的:
   ck_div.v  j1.v  reset_gen.v  topj1.v  trig_watchdog.v  uart.v  watchdog.v
verilog/coregen 目錄下的:
   pixfifo.xco
lib/mac 目錄下的:
   crc_chk.v  gmux.v   mac.v       reconciliation.v  rx_raw.v     tx_engine_raw.v
   crc_gen.v  greg.v   mii_mgmt.v  rx_engine_raw.v   rx_usr_if.v
   rx_pkt_fifo.v  tx_raw.v
lib/mac/xilinx/spartan3e 目錄下的:
   device_ODDR.v
lib/mac/xilinx/spartan3e/coregen 目錄下的:
   rx_pkt_fifo_sync.xco
   rxfifo.xco
   txfifo.xco
synth 目錄下的:   wge100_RevC_Camera.ucf

我並未加入所有的 .v 檔,只是先加入 topj1.v ,然後再依據 Design Hierarchy 顯示缺少的元件來加入需要的 .v 檔。當有 .v 和 .xco 檔時我選擇 .xco 檔。最後必須加入 wge100_RevC_Camera.ucf 以解決一個 Error。

如此,就可以從 Synthesis 一直做到 Design implementation 得到 bitstream。

在這之後,我想我必須依據 SP601 的硬體設計來修改 routing,以便讓 Ethernet 真的能跑起來。這樣,就可以透過 Ethernet 和 J1 CPU 上的 Forth 程式溝通了。

J1 Forth CPU 研究之一:Camera Firmware 的程式結構

這是一系列文章中的第一篇,這系列的文章是要解讀 J1 CPU 的設計以及在其上的 Forth 語言的使用法。

在這一篇文章中提及的 J1 被應用於 Camera,其 Forth 程式可以在以下目錄中找到:

https://github.com/chengchangwu/wge100_driver/blob/hydro-devel/wge100_camera_firmware/src/firmware

這個 Forth 以 Gforth 為 Host,J1 Forth CPU 為 Target。

首先,看看這個目錄裡最重要的幾個檔案:
  1. main.fs:這是主程式
  2. crossj1.fs:這是 J1 的 Cross compiler
  3. basewords.fs:在這兒,以 j1 的 assembler 來定義了基本的 Forth 指令。
對許多初學 Forth 的人來說,Cross compiler 無疑是最神秘玄奇之物了。因此讓我們先看看它。只有 512 行,真的很短,在裡面最重要的三個命令是:
  1. meta:執行 meta 後就可以開始定義 J1 的 Cross compiler (或稱為 metacompiler) 了。
  2. target:執行 target 後,之後定義的指令或是資料結構最後都會被放到 target 裡,也就是 j1 16k 的 RAM 裡。
  3. j1asm:執行 j1asm 後可以開始定義 j1 的 assembler。
再來,看看 main.fs 吧,在這個檔案中我們可以看出 cross compiler 是怎麼被使用的。首先,include crossj1.fs 載入了 metacompiler,再來執行 meta,定義了一些常數以及 basewords,最後執行 target,include hwwge.fs 及 boot.fs,然後開始定義 Camera 的應用程式。其中,hwwge.fs 以及位於 boot.fs 之後的程式都只是針對 camera 這個應用,在此就不詳提了,只說明 boot.fs 和在檔案尾端的 0jump。
  1. boot.fs:boot loader,它使用 spi 從 flash 載入整個 Forth 系統。它佔據了記憶體 3e00H 以後的空間,從檔案尾端的 h# 3e00 org 得知它從 3e00H 處開始執行。目前我還不清楚 boot loader 又是怎麼被誰放到 RAM 的 3e00H 後的空間中。這等待以後更瞭解時再向大家報告。
  2. 0jump:在 main.fs 尾端的 0jump 是主程式冷起動的位置。從之前的 0 org 可以知道它的 RAM 位址 是 0。由於有一行 h# 3e00 ubranch 被放在註解中,而 3e00H 是 boot loader 的開始位置,因此我們知道 boot loader 並沒有真的被使用。緊接在那行的是 main ubranch,所以,程式一開始會跳到 main,也就是這個 camera 的應用程式。

2011年9月14日 星期三

學習 Verilog HDL

因為工作需要使用 Xilinx FPGA,我開始學習 FPGA 以及 Verilog。
以下是我的學習過程。

我買了兩本原文書,但幫助不大。目前只讓我知道有 WebPACK 這玩意。

[2011/10/08修正:在開始閱讀我要研究的 j1 Forth CPU 後,其中的一本,Digital Dsign and Verilog HDL Fundamentals 開始有幫助,我覺得這一本寫得很清楚。不過,它缺少了 Verilog 2001 的內容,如 generate/genvar 等。對 generate/genvar 等的說明可以參考 "The IEEE Verilog 1364-2001 Standard, What's New, and Why You Need It" 一文。
]

下載 WebPACK。

閱讀 Xilinx ISE In-Depth Tutorial:
http://www.xilinx.com/support/documentation/sw_manuals/xilinx13_1/ise_tutorial_ug695.pdf

至於 Verilog,弟弟傳了 OVIVerilog HDL LRM Version 1.0 做為參考手冊。

此外,以下連結提供了許多資訊:http://www.asic-world.com/verilog/verilinks.html

以下連結中的 on-line reference guides 也很值得參考: http://www.sutherland-hdl.com/reference-guides.php 。







2011年9月3日 星期六

Why EtherCAT?

以下摘錄自 EtherCAT - the Ethernet fieldbus 這篇文章。

EtherCAT 的特點:
  1. real time 能力。
  2. 多種 Topology。商用 Ethernet 採用星狀的 Topology。EtherCAT 可以使用星狀或環狀或所謂的 E-Bus,以減少配線降低成本。
  3. Only full duplex,分離的 Tx, Rx 線。
  4. Without collision detection and retransmission.
  5. Master/slave 架構。
  6. Master 不需特別的晶片。因此任何有商用 Ethercat 的電腦都可以當 master。
  7. 可和其他 Ethernet 裝置並存於同一 Bus,可以使用商用的 switch。
  8. 就算是將 2bit 的 I/O 獨立做成一 slave 也不會增加太多成本。
  9. 最大效能。Ethernet 頻寬儘量用於傳送 user 的 data。
  10. Short cycle time, < 100 microsecond。
每個裝置都有 64k bytes 的 memory。
EtherCAT 的 telegrams 有兩種可能結構,使用 EtherCAT Header 或是 UDP Header。如果使用 EtherCAT Header ,則第一個裝置必須是一個能處理 MAC 定址的裝置。

EtherCAT 的 telegrams 內有多個 EtherCAT commands,一般來說,一個 command 針對一個 device,但是為了要處理極小的,比如只有 2bit 的 I/O devices,EtherCAT 使用 FMMU 使得一個 EtherCAT command 可以下給多個 devices。FMMU 的 logical address space 是 4GB,因此,對 master 來說,裝置可被視為 distributed memory。

遠距離傳輸時可以使用一般的 Ethernet Cable 或是光纖。較近距離的裝置可以使用以 LVDS 為基礎的 E-Bus。唯一要注意的是必須滿足 EtherCAT full duplex 的要求。

EtherCAT Slave 會從 ethernet telegram 讀取資料,並能插入資料交給下一個 slave。由最後一個 slave 將所有 slaves 的資料回傳給第一個 slave,再由第一個 slave 回傳給 master。(在文章中另一部份說最後一個 slave 將資料回傳給 master。這可能和 EtherCAT telelgrams 的兩種結構有關。當採用 EtherCAT Header 時,第一個 Device 會是有 MAC 定址能力的 device。)


EtherCAT Slave 能偵測斷線而將要送給下一站的資料回傳給上一站。作法請見文件。

EtherCAT Slave 也能偵測斷線回復。作法請見文件。

EtherCAT 使用distributed clocks 來保証所有 slaves 的時間一致。Master 每隔一段時間就會送出特別的 telegram 以較正時間。

除了以上所列,EtherCAT 還提供了不同的定址方法,internode 通訊,同時也允許傳送 Ethernet 訊息。詳情請見文件。

2011年9月2日 星期五

自動化機械展中的工業網路大戰

昨日參觀台北南港展覽館的自動化機械展,想瞭解 EtherCAT 中 E-Bus 的長相。因為,從 Beckhoff 公司的圖片中只見到每個 I/O module 有六個名為 E-Bus 的突出物,完全無法想像當插入新模組時,這新模組怎麼和其他模組相連。

在展場中找尋 Beckhoff 的 EtherCAT 模組時,我先遇到了三菱、Panasonic、Proface、凌華等公司的 PLC。Panasonic 的擴充模組靠一個小接口和主機相連,那接口的形狀限制了擴充模組插拔的方式:只能從左側或是右側插拔,無法從正面插拔。因此,當有多個延伸模組時,必須移動其他的模組才能拔出想要檢修的模組。

為此,當我看到 Beckhoff 的 E-Bus 時,不得不為其設計驚嘆。E-Bus 由六片大銅片構成,模組和模組靠銅片的彈性相接,彷彿放大版的 Ethernet 接口。它可以正面插拔。

對於 E-Bus 我還有一個疑問:它為什麼要用這麼寬的銅片?因為要提供絕佳的接觸嗎?

在現場中,Mechatrolink III 、 SSCNET 及 CC-Link 各有其支持者。PLC 的大廠 OMROM 從 Mechatrolink II 倒向 EtherCAT,Mechatrolink III 支持者安川(Yaskawa)悄悄提供了 EtherCAT 界面的 Drives,讓我感覺到 EtherCAT 在未來的戰事上,至少在必須整合運動控制的市場,獲勝。

會場中展示 Beckhoff 產品的公司員工告訴我台達電計劃明年推出 EtherCAT 的產品,而台達電一直抱怨授權費太高。支持 Mechatrolink III 的安川員工告訴我 FPGA IP 的授權費用高達數十萬。這些,讓我有些許震驚。如果 FPGA IP 的授權費高達數十萬,那顯然不是我目前能採用的。同時,如果台達明年會推出 EtherCAT 產品,那麼我們引進台達 DMCNET 的計劃是否該延後呢?

此外,我發覺自已的一項疏失:EtherCAT似乎不能和 Ethernet/IP 並用?

在展場中另一項吸引我目光的是凌華的 MotionNet,透過在馬達 Drive 端小小的轉接器,MotionNet 可以控制不同公司的馬達。可惜,使用的是 Pulse command,不是我想走的方向。

Note: 以下是我之前詢問過的 EtherCAT IP Core 的價格,那每年都要繳的維護費,真讓人無語啊...


2011年8月11日 星期四

Java NIO 的教材

工作需要我必須學習 Java NIO。在網路上找到了以下兩個連結:

2011年8月8日 星期一

以 batik 讀取 svg 中的 points

如何透過 rhino 及 batik 讀取 SVG 中的 points?

以下是我的程式:


importClass(org.apache.batik.parser.PointsHandler);
importClass(org.apache.batik.parser.PointsParser);

var pp = new PointsParser();
var ph = new PointsHandler({point: function(x, y) {print('('+ x + ',' + y + ')');}});
pp.setPointsHandler(ph);

測試一下:
pp.parse('50,375,150,375 150,325 250,325 250,375,350,375 350,250 450,250 450,375,550,375 550,175 650,175 650,375,750,375 750,100, 850,100 850,375, 50,375 950,25 1050,25 1050,375,1150,375');