TD4のエミュレータを作った
e-tipsmemo.hatenablog.com
が、それだけではつまらないので、GUIを被せた。
RustのGUIライブラリicedを使用した。
シンプルそうだったからというのが選んだ理由。
GitHub - katakanan/td4-gui
Ticker
Icedは周期を指定してイベントを起こす機能が備わっているのでそれを使う。
サンプル StopWatch
この周期でエミュレータのfetch-decode-exec実行を回している。
fn subscription(&self) -> Subscription<Message> { match self.state { State::Idle => Subscription::none(), State::Active => { time::every(std::time::Duration::from_millis(self.period)).map(|_| Message::Tick) } } }
Run-Stop-Step-Resetボタン
CPUの作り方の実機でもResetボタンとStepボタンがあった気がする。
let run = Button::new(&mut self.run, Text::new("Run")) .padding(10) .on_press(Message::Run) .style(self.theme); let step = Button::new(&mut self.step, Text::new("Step")) .padding(10) .on_press(Message::Step) .style(self.theme); let stop = Button::new(&mut self.stop, Text::new("Stop")) .padding(10) .on_press(Message::Stop) .style(self.theme); let reset = Button::new(&mut self.reset, Text::new("Reset")) .width(Length::from(300)) .padding(10) .on_press(Message::Reset) .style(self.theme);
Slider
1Hz動作だと遅いので、
Tickerの周期を変更できるようになっている。
10ミリ秒だとTickerの割り込みが多くなって、ボタン操作に影響が出ている気がする。
(UIと処理を別スレッドにしたりする必要があるが面倒なのでしない)
let slider = Slider::new( &mut self.slider, 100.0..=1000.0, self.period as f64, Message::SliderChanged, );
ビット操作
一番大変?だった?
まず最初はチェックボックスでこれを行おうとしたが、チェックボックスはメッセージにチェックされているかどうかと言うboolの値が1個しか送れないので、
チェックボックスに同じメッセージを設定すると、どのアドレスの何ビット目なのかを判別できない。
checkboxの引数の型がF: 'static + Fn(bool) となっている。
icedのライブラリ内
pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self where F: 'static + Fn(bool) -> Message, { Checkbox { is_checked, on_toggle: Box::new(f), label: label.into(), width: Length::Shrink, size: <Renderer as self::Renderer>::DEFAULT_SIZE, spacing: Renderer::DEFAULT_SPACING, text_size: None, font: Renderer::Font::default(), text_color: None, style: Renderer::Style::default(), } }
Buttonではon_eventのメッセージにはEnumで定義されたメッセージ with Tupleを与えられるので、
順にaddr:usize、bit:u8、現在の状態:boolと決めておく。
#[derive(Debug, Clone, Copy)] pub enum Message { Reset, Tick, Run, Step, Stop, RomEdit(usize, u8, bool), InputEdit(u8, bool), SliderChanged(f64), }
ボタンをnewするときに同時にボタンの見た目を指定できるので、exampleにあったLightテーマとDarkテーマを1と0で切り替えて使う。
それをbit数だけ並べる。
method chainでbuttonのarrayを作ってRowにpushする。
(Rowはコンテンツを左から横にレイアウトしていく)
impl InputHalfByte { pub fn crate_layout(&mut self, value: &u8) -> Row<Message> { self.buttons(value) .into_iter() .fold(Row::new().spacing(1), |row, button| row.push(button)) } fn buttons(&mut self, value: &u8) -> Vec<Button<Message>> { self.bit_state .iter_mut() .enumerate() .map(|(i, state)| { let bit = value & (0x01 << i) != 0; let btn = Button::new(state, bit2text(bit)) .on_press(Message::InputEdit(i as u8, bit)) .style(bit2style(bit)); btn }) .rev() .collect::<_>() } }
romも8bitにしたものをColumnで並べる。
lifetimeやら所有権やらでここが一番よくわからなかった。
registerごとにgetとか実装したほうが良かったのか?
(エラーが出ないように修正しただけなので結局よくわかっていない)
LED表示
レジスタの情報の表示。
pub fn led4bit(halfbyte: &u8) -> Row<Message> { (0..4) .into_iter() .rev() .fold(Row::new().spacing(1), |row, i| { row.push(circle::Circle::new( 10.0, bit2color(&((halfbyte & (0x01 << i)) != 0)), )) }) }
icedのexampleにあったcircleを改造したものを並べている。
完成したもの
いろいろとこだわれるところはあると思うが、これでおしまい。
バグに気づいたら直すかもしれない。

