I try doing [Nand to Tetris](https://www.nand2tetris.org/) in Rust.
これまでは状態を持たないチップだったのに対し、ここからは状態を持つチップになります。実世界では Nand 等から作れるらしいですが、プログラム上ではループが発生してしまうとややこしかったり再現不可能だったりするので、 Dff は Rust 言語の if 文等を使い実装していきます。また、その他も状態を持つために struct として実装していきます。
input
と output
の2つを内部状態として保持し、 clock
に対応すれば実装できます。
pub struct Dff {
input: bool,
output: bool,
clock: bool,
}
impl Dff {
pub fn new() -> Self {
Self {
input: false,
output: false,
clock: false,
}
}
pub fn set_input(&mut self, input: bool) {
self.input = input;
}
pub fn get_output(&self) -> bool {
self.output
}
pub fn tick(&mut self) {
self.set_clock(true);
self.set_clock(false);
}
pub fn set_clock(&mut self, clock: bool) {
if !self.clock && clock {
self.output = self.input;
}
self.clock = clock;
}
}
Bit という名前ですが、 1bit のレジスタです。入力時に load
フラグが立っているかどうかで分岐すれば実装できます。
pub struct Bit {
load: bool,
dff: Dff,
}
impl Bit {
pub fn new() -> Self {
Self {
load: false,
dff: Dff::new(),
}
}
pub fn set_load(&mut self, load: bool) {
self.load = load;
}
pub fn set_input(&mut self, input: bool) {
self.dff.set_input(mux(self.get_output(), input, self.load));
}
pub fn get_output(&self) -> bool {
self.dff.get_output()
}
pub fn tick(&mut self) {
self.set_clock(true);
self.set_clock(false);
}
pub fn set_clock(&mut self, clock: bool) {
self.dff.set_clock(clock);
}
}
こちらは正真正銘 16bit のレジスタです。 Bit を16個並べれば実装できますが、後述の理由で 16bit をまとめて扱える Dff として Dff16 を実装し、それを用いて実装しました。
pub struct Register {
load: bool,
dff: Dff16,
}
impl Register {
pub fn new() -> Self {
Self {
load: false,
dff: Dff16::new(),
}
}
pub fn set_load(&mut self, load: bool) {
self.load = load;
}
pub fn set_input(&mut self, input: &[bool; 16]) {
self.dff
.set_input(&mux16(&self.get_output(), input, self.load));
}
pub fn get_output(&self) -> [bool; 16] {
self.dff.get_output()
}
pub fn tick(&mut self) {
self.set_clock(true);
self.set_clock(false);
}
pub fn set_clock(&mut self, clock: bool) {
self.dff.set_clock(clock);
}
}
Ram 系は単純に Register を並べれば実装できます。ただし、 Ram16k になるとレジスタ数が多すぎて Register を Bit 16個分として実装してしまうとスタックオーバーフローしてしまいました。なので、前述の通り Register を Dff16 を用いて実装することにしました。今後も、スタックオーバーフロー等する場合には 16bit を u16
として扱うかもしれません。
pub struct Ram8 {
registers: [Register; 8],
address: usize,
}
impl Ram8 {
pub fn new() -> Self {
Self {
registers: [
Register::new(),
Register::new(),
Register::new(),
Register::new(),
Register::new(),
Register::new(),
Register::new(),
Register::new(),
],
address: 0,
}
}
pub fn set_load(&mut self, load: bool) {
for i in 0..8 {
self.registers[i].set_load(load);
}
}
pub fn set_input(&mut self, input: &[bool; 16], address: &[bool; 3]) {
self.address =
(address[0] as usize) << 2 | (address[1] as usize) << 1 | address[2] as usize;
self.registers[self.address].set_input(input);
}
pub fn get_output(&self) -> [bool; 16] {
self.registers[self.address].get_output()
}
pub fn tick(&mut self) {
self.set_clock(true);
self.set_clock(false);
}
pub fn set_clock(&mut self, clock: bool) {
for i in 0..8 {
self.registers[i].set_clock(clock);
}
}
}
Pc の実装も単純に、 increment
, load
, reset
の入力を保持しておいて Register と組み合わせれば実装できます。
pub struct Pc {
register: Register,
increment: bool,
load: bool,
reset: bool,
}
impl Pc {
pub fn new() -> Self {
let mut register = Register::new();
register.set_load(true);
Self {
register,
increment: false,
load: false,
reset: false,
}
}
pub fn set_increment(&mut self, increment: bool) {
self.increment = increment;
}
pub fn set_load(&mut self, load: bool) {
self.load = load;
}
pub fn set_reset(&mut self, reset: bool) {
self.reset = reset;
}
pub fn set_input(&mut self, input: &[bool; 16]) {
self.register.set_input(&mux16(
&mux16(
&mux16(
&self.get_output(),
&inc16(&self.get_output()),
self.increment,
),
input,
self.load,
),
&[false; 16],
self.reset,
));
}
pub fn get_output(&self) -> [bool; 16] {
self.register.get_output()
}
pub fn tick(&mut self) {
self.set_clock(true);
self.set_clock(false);
}
pub fn set_clock(&mut self, clock: bool) {
self.register.set_clock(clock);
}
}
これで Project 3 の全てのチップを実装しました。今回はパズル的な要素は少なく、8個並べるのが面倒なくらいで難しさはなかったですが、今後のことを考えると 16bit を bool
の配列としてではなく u16
として扱ったほうが良かったかもしれません。ただ、そうなるとこれまでの 16bit 版も全て u16
にすることになるので必要に迫られたときに変えようと思います。