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|紆余曲折の過程は、このとおり。)%>