8 bit CPU
2009年7月21日
紆余曲折の結果、結局 8 bit のオーソドックスなCPUに落ち着きそう。
仕様は、次のとおり。
version 0.0.6.1
特徴としては、すべての命令を2バイトとしたこと。これで、回路の簡略化が見込める。
汎用レジスタは、X, Y の2つ。フラグレジスタの下位6ビットは、データ読み書きのセグメントとして用いる。
例によって、スタック領域は256バイトのみ。ただし、この領域の一部(16バイトの予定)は、レジスタ操作の補助に用いる。
アドレスラインは、14ビット。ただし、プログラム可能なアドレス領域は、12ビット(4Kバイト)。これにより、すべてのジャンプ命令がきれいに2バイトに収まった。この大きさのアドレス領域でも円周率計算プログラムが書ける事は、PICに組み込んだプログラムで実証済み。
push/pop命令を適当に散らばらせることで、たぶん、この部分の回路の簡略化が見込める。
PHPで、シミュレーションを行いながら設計している。現在のところ、以下のようなスクリプト。
<%media(20090722-old versions.zip|紆余曲折の過程は、このとおり。)%>
仕様は、次のとおり。
version 0.0.6.1
data line: 8 bits address line: 14 bits (8 bits + 6 bits; 256 x 64) registors X(8), Y(8), F(8), PC(12), SP(8) F: Z, C, S5, S4, S3, S2, S1, S0 (Z, C: zero and carry flags; S: segment registor) stack segment is always 0x3f program can be written from 0x0000 to 0x0fff All 2 byte commands 0x00, 0x01, ... , 0x0f: goto, operand: 12 bits. 0x10, 0x11, ... , 0x1f: call, operand: 12 bits. 0x20, 0x21, ... , 0x27: X=XX, [X]=XX, X=(XX), (XX)=X, Y=XX, [Y]=XX, X=(XX), (XX)=X 0x28, 0x29, ... , 0x2f: (see below) 0x2800 - 0x280f: X=X (NOP), X=Y, X=F, X=SP, reserved, X=[Y], 0x2810 - 0x281f: Y=X, Y=Y, Y=F, Y=SP, Y=[X], reserved, 0x2820 - 0x282f: F=X, F=Y, F=F, SP=SP, F=[X], F=[Y], 0x2830 - 0x283f: SP=X, SP=Y, SP=F, SP=SP, SP=[X], SP=[Y], 0x2880 - 0x288f: [X]=X, [X]=Y, [X]=F, [X]=SP, reserved, reserved, 0x2890 - 0x289f: [Y]=X, [Y]=Y, [Y]=F, [Y]=SP, reserved, reserved, 0x2900 - 0x290f: [SP]=X, [SP]=Y, [SP]=F, 0x2920 - 0x292f: push X, push Y, push F, SP-- 0x2940 - 0x294f: [++SP]=X, [++SP]=Y, [++SP]=F, 0x2960 - 0x296f: [SP+1]=X, [SP+1]=Y, [SP+1]=F, 0x2980 - 0x298f: X=[SP], Y=[SP], F=[SP], 0x29a0 - 0x29af: X=[SP--], Y=[SP--], F=[SP--], 0x29c0 - 0x29cf: pop X, pop Y, pop F, SP++ 0x29e0 - 0x29ef: X=[SP+1], Y=[SP+1], F=[SP+1], 0x30, 0x31, ... , 0x38: (XX)=X+Y, (XX)=X-Y, (XX)=X&Y, (XX)=X|Y, (XX)=X+Y+C, (XX)=X-Y-C, (XX)=X^Y, (XX)=~X 0x38, 0x31, ... , 0x3f: X=++(XX), X=--(XX), reserved x2, Y=++(XX), Y=--(XX), reservedx2 0x40, 0x41, ... , 0x4f: jz, operand: 12 bits. 0x50, 0x51, ... , 0x5f: jc, operand: 12 bits. 0x60, 0x61, ... , 0x6f: jnz, operand: 12 bits. 0x70, 0x71, ... , 0x7f: jnc, operand: 12 bits. 0x80, 0x81, ... , 0x8f: reserved 0x90, 0x91, ... , 0x9f: reserved 0xa0, 0xa1, ... , 0xaf: reserved 0xb0, 0xb1, ... , 0xbf: reserved 0xc0, 0xc1, ... , 0xcf: reserved 0xd0, 0xd1, ... , 0xdf: reserved 0xe0, 0xe1, ... , 0xef: reserved 0xf0, 0xf1, ... , 0xff: reserved (XX) = [3Fh:XX]; used as registors; genelary, F0-FF (16 registors) are used.
特徴としては、すべての命令を2バイトとしたこと。これで、回路の簡略化が見込める。
汎用レジスタは、X, Y の2つ。フラグレジスタの下位6ビットは、データ読み書きのセグメントとして用いる。
例によって、スタック領域は256バイトのみ。ただし、この領域の一部(16バイトの予定)は、レジスタ操作の補助に用いる。
アドレスラインは、14ビット。ただし、プログラム可能なアドレス領域は、12ビット(4Kバイト)。これにより、すべてのジャンプ命令がきれいに2バイトに収まった。この大きさのアドレス領域でも円周率計算プログラムが書ける事は、PICに組み込んだプログラムで実証済み。
push/pop命令を適当に散らばらせることで、たぶん、この部分の回路の簡略化が見込める。
PHPで、シミュレーションを行いながら設計している。現在のところ、以下のようなスクリプト。
<?php define('_DISPLAY_EVERY',1); //define('_SHOW_MODE','hex'); define('_SHOW_MODE','dec'); // begin global code code::init(); while (true) { $line=code::get_line(); if (strlen($line)==0 || $line{0}=='#') { reg::$msg=$line; display(); continue; } if ($line=='EXIT') break; code::execute($line); display(); } display(true); exit('The program ended normally'); // end global code class code{ static public $code=array(); static public $label=array(); static public $namespace=array(); static public $linenum=0; static public function init(){ $rawcode=raw_code($start); reg::$pc=$start; self::$linenum=$start+count($rawcode); $namespace=''; for ($n=$start;$n<self::$linenum;$n++) { $line=$rawcode[$n-$start]; $line=preg_replace('#//.*#','',$line); $line=trim($line); if (preg_match('/^[0-9a-zA-Z_]+\:$/',$line,$m)) { $lbl=strlen($namespace) ? $namespace.'_':''; $lbl=strtoupper($lbl.$m[0]); if (isset(self::$label[$lbl])) { exit("The same label exists more than twice: '$lbl'."); } self::$label[$lbl]=$n; $line='#['.$line.']'; } else if (preg_match('/^namespace($|\s.+$)/',$line,$m)){ $namespace=strtoupper(trim($m[1])); $line='#['.$line.']'; } else $line=strtoupper($line); self::$code[$n]=$line; self::$namespace[$n]=$namespace; } } static public function get_line(){ if (self::$linenum<=reg::$pc) error('PC reached the end'); self::current_namespace(self::$namespace[reg::$pc]); if (!isset(self::$code[reg::$pc])) error('PC is out of range'); return self::$code[reg::$pc++]; } static public function current_namespace($ns=false){ static $namespace=''; if ($ns===false) return $namespace; $namespace=$ns; } static public function jump($lbl){ if (substr($lbl,-1)!=':') $lbl.=':'; if (isset(self::$label[$lbl])) $lbl=$lbl; elseif (isset(self::$label[self::current_namespace().'_'.$lbl])) $lbl=self::current_namespace().'_'.$lbl; elseif (isset(self::$label[substr($lbl,0,-1).'_'.$lbl])) $lbl=substr($lbl,0,-1).'_'.$lbl; //elseif (isset(self::$label['_'.$lbl])) $lbl='_'.$lbl; else { print_r(self::$label); error("Label doesn't exist: $lbl"); } reg::$pc=self::$label[$lbl]; } static public function execute($line){ reg::$msg=$line; if (!preg_match('/^(.+)(\=|\s)(.+)$/',$line,$c)) $c=array($line,$line,'',''); if (method_exists(__CLASS__,"do_$c[1]_$c[3]")) $err=call_user_func_array(array(__CLASS__,"do_$c[1]_$c[3]"),$c); else if (method_exists(__CLASS__,"do_$c[1]")) $err=call_user_func_array(array(__CLASS__,"do_$c[1]"),$c); else { $err='error'; switch ($c[1]) { case 'SP++': $err=pop(); break; case 'SP--': $err=push(); break; case '[X]': $err=call_user_func_array(array(__CLASS__,'do__X_'),$c); break; case '[Y]': $err=call_user_func_array(array(__CLASS__,'do__Y_'),$c); break; case '[SP]': case '[++SP]': case '[SP+1]': $err=call_user_func_array(array(__CLASS__,'do__SP_'),$c); break; default: if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c[1])) { $err=call_user_func_array(array(__CLASS__,'do_XX_'),$c); } break; } } if ($err==='error') error("Syntax error: $line"); } static private function do_NOP(){ } static private function do_X($line,$c1,$c2,$c3){ reg::$x=byte(self::_XYFSP($c3)); } static private function do_Y($line,$c1,$c2,$c3){ reg::$y=byte(self::_XYFSP($c3)); } static private function do_F($line,$c1,$c2,$c3){ reg::set_f(byte(self::_XYFSP($c3))); } static private function do_SP($line,$c1,$c2,$c3){ reg::$sp=byte(self::_XYFSP($c3)); } static private function do__X_($line,$c1,$c2,$c3){ write(reg::$ds*256+reg::$x,self::_XYFSP($c3)); } static private function do__Y_($line,$c1,$c2,$c3){ write(reg::$ds*256+reg::$y,self::_XYFSP($c3)); } static private function do__SP_($line,$c1,$c2,$c3){ switch($c1){ case '[SP]': $addr=reg::$sp; break; case '[++SP]': pop(); $addr=reg::$sp; break; case '[SP+1]': $addr=(reg::$sp+1) % 256; break; default: return 'error'; } write_ss($addr,self::_XYFSP($c3)); } static private function _XYFSP($c3){ switch ($c3) { case 'X': return reg::$x; case '[X]': return read(reg::$ds*256+reg::$x); case 'Y': return reg::$y; case '[Y]': return read(reg::$ds*256+reg::$y); case 'F': return reg::get_f(); case 'SP': return reg::$sp % 256; case '[SP]': return read_ss(reg::$sp); case '[SP--]': $data=read_ss(reg::$sp); reg::$sp=(reg::$sp+255)%256; return $data; case '[SP+1]': return read_ss((reg::$sp+1)%256); default: if (preg_match('/^(.*)\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) { switch($m[1]){ case '++': $data=set_flags(read_ss(hexdec($m[2]))+1); write_ss(hexdec($m[2]),$data); return $data; case '--': $data=set_flags(read_ss(hexdec($m[2]))-1); write_ss(hexdec($m[2]),$data); return $data; case '': return read_ss(hexdec($m[2])); default: break; } } return byte($c3); } } static private function _GOTO($c3){ code::jump($c3); } static private function do_GOTO($line,$c1,$c2,$c3){ self::_GOTO($c3); } static private function do_JMP($line,$c1,$c2,$c3){ self::_GOTO($c3); } static private function do_CALL($line,$c1,$c2,$c3){ push((int)(reg::$pc/256)); push(reg::$pc%256); self::_GOTO($c3); } static private function do_RETURN($line,$c1,$c2,$c3){ reg::$pc=pop(); reg::$pc+=pop()*256; } static private function do_RET($line,$c1,$c2,$c3){ self::do_RETURN($line,$c1,$c2,$c3); } static private function do_JZ($line,$c1,$c2,$c3){ if (reg::$z==1) self::_GOTO($c3); } static private function do_JNZ($line,$c1,$c2,$c3){ if (reg::$z==0) self::_GOTO($c3); } static private function do_JC($line,$c1,$c2,$c3){ if (reg::$c==1) self::_GOTO($c3); } static private function do_JNC($line,$c1,$c2,$c3){ if (reg::$c==0) self::_GOTO($c3); } static private function do_XX_($line,$c1,$c2,$c3){ switch($c3){ case 'X': $data=reg::$x; break; case 'Y': $data=reg::$y; break; case 'X+Y': $data=set_flags(reg::$x+reg::$y); break; case 'X-Y': $data=set_flags(reg::$x-reg::$y); break; case 'X&Y': $data=set_flags(reg::$x®::$y); break; case 'X|Y': $data=set_flags(reg::$x|reg::$y); break; case 'X+Y+C': $data=set_flags(reg::$x+reg::$y+reg::$c); break; case 'X-Y-C': $data=set_flags(reg::$x-reg::$y-reg::$c); break; case 'X^Y': $data=set_flags(reg::$x^reg::$y); break; case '~X': $data=set_flags(~reg::$x); break; default: return 'error'; } write_ss(hexdec(substr($c1,1,-1)),$data); } static private function do_PUSH($line,$c1,$c2,$c3){ switch($c3) { case 'X': return push(reg::$x); case 'Y': return push(reg::$y); case 'F': return push(reg::get_f()); case 'SP': return push(reg::$sp); default: /*if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) { return push(read_ss(hexdec($m[1]))); }*/ return 'error'; } } static private function do_POP($line,$c1,$c2,$c3){ switch($c3) { case 'X': return reg::$x=pop(); case 'Y': return reg::$y=pop(); case 'F': return reg::set_f(pop()); case 'SP': return reg::$sp=pop(); default: /*if (preg_match('/^\(([0-9A-F][0-9A-F])\)$/',$c3,$m)) { return write_ss(hexdec($m[1]),pop()); }*/ return 'error'; } } } class reg{ static public $x=0; // X registor static public $y=0; // Y registor static public $c=0; // carry flag static public $z=0; // zero flag static public $ds=0; // data segment static public $sp=0; // stack pointer static public $pc=0; // program counter (line number of code array) static public $msg=''; // message string being shown static public function get_f(){ return self::$c*128+self::$z*64+self::$ds; } static public function set_f($data){ $data=byte($data); reg::$z=($data & 0x80) ? 1:0; reg::$c=($data & 0x40) ? 1:0; reg::$ds=$data % 64; } } function push($data='none'){ if ($data!=='none') write_ss(reg::$sp,$data); reg::$sp=(255+reg::$sp) % 256; } function pop(){ reg::$sp=(1+reg::$sp) % 256; return read_ss(reg::$sp); } function set_flags($data){ reg::$c=1; if ($data<0) $data=$data+256; elseif (255<$data) $data=$data-256; else reg::$c=0; if ($data==0) reg::$z=1; else reg::$z=0; return byte($data); } function display($force=false){ static $counter=0; $counter++; if ($counter<_DISPLAY_EVERY && !$force) return; $counter=0; $msg=reg::$msg; ob_start(); if (_SHOW_MODE=='dec') { // decimal mode; address 0x00 - 0x07 for ($x=0;$x<8;$x++) { $d=read($x); if (31<$d) { echo '_'; } elseif (0<=$d && $d<=9) { echo $d; } elseif (16<=$d && $d<=25) { echo ($d-16).'.'; } else { echo '?'; } } } else { // hex mode; address 0x10 - 0x13 for ($x=0x13;0x10<=$x;$x--) show_hex(read($x),2); } echo ' '; echo 'X:',show_hex(reg::$x,2).' '; echo 'Y:',show_hex(reg::$y,2).' '; echo 'C:',show_hex(reg::$c,1).' '; echo 'Z:',show_hex(reg::$z,1).' '; echo 'S:',show_hex(reg::$ds,2).' '; echo 'SP:',show_hex(reg::$sp,2).' '; echo 'PC:'.reg::$pc.' '; $msg=ob_get_clean().$msg; if (strlen($msg)<80) echo "$msg\n"; else substr($msg,0,80); reg::$msg=''; } function show_hex($dec,$f){ $val=str_repeat('0',$f).strtoupper(dechex($dec)); echo substr($val,-$f); } function read($addr){ $data=memory($addr); reg::$msg.=' '.dechex($addr).':'.dechex($data); return $data; } function write($addr,$data){ $org=memory($addr); memory($addr,$data); reg::$msg.=' '.dechex($addr).':'.dechex($org).'->'.dechex(byte($data)); } function read_ss($addr){ return read(0x3f00+byte($addr)); } function write_ss($addr,$data){ write(0x3f00+byte($addr),$data); } function memory($addr,$data='read'){ static $m; if (!isset($m)) $m=array_fill(0,0x4000,0x55); if ($data==='read') return $m[$addr]; $data=byte($data); $m[$addr]=$data; } function byte($data){ return check_value($data,8); } function check_value($data,$bit){ $max=pow(2,$bit); if (preg_match('/^0x([0-9A-F]+)$/',$data,$m)) $data=hexdec($m[1]); elseif (preg_match('/^([0-9A-F]+)H$/',$data,$m)) $data=hexdec($m[1]); elseif (!is_numeric($data)) error("Data must be numeric: $data"); if (0<=$data && $data<=$max) return (int)$data; error("Data must be between 0 and $max: $data"); } function error($text=''){ $linenum=reg::$pc; $db=''; foreach(debug_backtrace() as $d){ $db.=$d['line'].','; } echo "Error occured at line $linenum (PHP:$db)\n$text\n"; exit; } function raw_code(&$ret){ ob_start(); $ret=__LINE__; ?> # initialize nop X=efH // Use 0x3ff0 - 0x3fff for registors SP=X X=1h // Use 0x0100 - 0x01ff for data strage F=X # start main call main exit namespace main main: X=FFh (F0)=X loop: X=(F0) [X]=6 X=--(F0) jnc loop // test add X=10h Y=20h (F0)=X (F1)=Y X=8 Y=0h call add // test mul8 # X=16 # Y=50 # call mul8 # push Y # Y=0 # [Y]=X # pop Y # X=4 # [X]=Y // test mul4 # X=0 # Y=5 # call mul4 # Y=0 # [Y]=X // carry check X=8 Y=0h # call carry_check X=0h (F0)=X X=8 Y=80h call copy X=80h call show // test push routines X=12h (F0)=X X=13h call push_f0 X=(F0) X=0h (F0)=X X=14h Y=15h call pop_f0 X=(F0) end: return ############################# # decimal mode show routine # # 8 bytes from X will be # # moved to 0x0000 to 0x0007 # ############################# namespace show show: (F0)=X X=7 (F1)=X // counter loop: X=(F0) // original address Y=[X] // data X=++(F0) push F X=0 F=X X=(F1) [X]=Y pop F X=--(F1) jnc loop return ####################### # Carry check routine # # X: byte length # # Y: pointer to data # ####################### namespace carry_check carry_check: (F0)=X (F1)=Y X=0 (F2)=X // carry loop: Y=(F1) X=[Y] Y=(F2) (F3)=X+Y // value temporary X=--(F0) jz end X=0 (F2)=X loop2: X=(F3) Y=10 (F3)=X-Y jc next X=++(F2) goto loop2 next: Y=(F1) [Y]=X X=++(F1) goto carry_check_loop end: return ################## # copy routine # # X: byte length # # Y: destination # # (F0): origin # ################## namespace copy copy: (F1)=X // byte length (F2)=Y // destination loop: Y=(F0) X=++(F0) X=[Y] Y=(F2) [Y]=X Y=++(F2) Y=--(F1) jnz loop return ################## # add routine # # X: byte length # # Y: &result # # (F0): &value 1 # # (F1): &value 2 # ################## namespace add add: push X push Y (F2)=X // byte length (F3)=Y // &result loop: Y=(F0) X=++(F0) X=[Y] (F4)=X // temporary space Y=(F1) X=++(F1) X=[Y] Y=(F4) (F4)=X+Y X=(F4) Y=(F3) [Y]=X X=++(F3) Y=--(F2) jnz loop pop Y pop X call carry_check return ###################### # 8 bit multiply # # X value 1 # # Y value 2 # # result will be in # # X and Y, # # low and high bytes # # respectively # ###################### namespace mul8 mul8: (F4)=Y // keeps value 2 (F2)=X // temp value lower byte X=0 (F3)=X // temp value higher byte (F0)=X // result lower byte (F1)=X // result higher byte X=1 (F5)=X loop: X=(F4) Y=(F5) (F6)=X&Y jz next X=(F0) Y=(F2) (F0)=X+Y X=(F1) Y=(F3) (F1)=X+Y+C next: X=(F2) Y=(F2) (F2)=X+Y X=(F3) Y=(F3) (F3)=X+Y+C X=(F5) Y=(F5) (F5)=X+Y jnz loop X=(F0) Y=(F1) return ###################### # 4 bit multiply # # X value 1 # # Y value 2 # # result will be in # # X # ###################### namespace mul4 mul4: (F0)=X // counter X=0 (F1)=X // result X=++(F0) loop: X=--(F0) X=(F1) jz end (F1)=X+Y goto loop end: return ################# # push routines # ################# namespace push f0: SP-- push X X=(F0) push_main: [SP]=Y SP++ SP++ Y=[SP+1] // get PCL [SP]=Y // set PCL SP++ pop Y // get PCH push X // set (F0) push Y // set PCH SP-- SP-- Y=[SP] pop X return f1: SP-- push X X=(F1) goto push_main: f2: SP-- push X X=(F1) goto push_main: f3: SP-- push X X=(F1) goto push_main: ################ # pop routines # ################ namespace pop f0: call pop_main (F0)=X pop X return pop_main: [SP]=Y SP++ SP++ pop Y // get PCL [SP]=X pop X // get PCH push Y // set PCL SP++ pop Y // get (F0) push X // set PCH X=Y // get (F0) SP-- SP-- SP-- SP-- Y=[SP] return f1: call pop_main (F1)=X pop X return f2: call pop_main (F2)=X pop X return f3: call pop_main (F3)=X pop X return ######################### # several calc routines # ######################### namespace calc rol: Y=(F0) push Y Y=X (F0)=X+Y+C X=(F0) pop Y (F0)=Y return rol_x: push Y call rol pop Y return rol_y: push X X=Y call rol Y=X pop X return ror: Y=(F0) push Y Y=X (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C (F0)=X+Y+C X=(F0) pop Y (F0)=Y return ror_x: push Y call ror pop Y return ror_y: push X X=Y call ror Y=X pop X return clear_carry: X=(F0) push X (F0)=~X pop X (F0)=X return shl_x: call clear_carry goto rol_x: shl_y: call clear_carry goto rol_y: xpp: push Y Y=(F0) push Y (F0)=X X=++(F0) pop Y (F0)=Y pop Y return ypp: push X X=(F0) push X (F0)=Y Y=++(F0) pop X (F0)=X pop X return <?php return preg_split('/(\r\n|\r|\n)/',ob_get_clean()); }
<%media(20090722-old versions.zip|紆余曲折の過程は、このとおり。)%>