Plaid CTF 2015 ECE's Revenge2 Write-up

1問だけ解いた。

http://play.plaidctf.com/files/final_8ce2c1db4ce9e96bcc859ded79fba21c.sv

SystemVerilogのコード。最終的にwinが1となるような入力を求めよという問題。

実際にシミュレータで動かせれば楽だったけど、いくつかシミュレーターを試しても動かなかったので、手で解いた。

問題のコードを次のように直す。

  • part_aからpart_dの中身を呼び出し元に展開
  • if (定数) となるif分を削除
  • reset_Lを~resetにというように、単にassingされている変数を置き換えていく
  • s_lとs_hはis_lの下位ビットと上位ビットなので、is_lに置き換え
  • IMO1-IMO4を使った処理は、 assign im_f = user_in[is_i] と等価

こうなる。

`default_nettype none
module testbench;
   logic clock, win;
   logic [9:0] counter;
   logic [31:0] user_in;

   datapath d(.*);

   initial begin
      clock = 0;
      // Fill in the constant such that when the circuit hits cycle 360, 
      // the logic "win" should be high. 
      user_in = 32'b????????????????????????????????;
      reset = 1;
      #5 clock = ~clock;
      #5 clock = ~clock;
      #5 clock = ~clock;
      reset <= #1 0;
      for(counter = 0; counter < 70; counter++) begin
        #5 clock = ~clock;
      end

   end
   
endmodule : testbench

module datapath(
    input logic clock
    input logic [31:0] user_in,
    output logic win);

   logic [3:0] r1_out, r2_out;
   logic i_g_o, reset, init;
   logic init_delay;
   logic [7:0] is_i;

   always @(posedge clock) begin
    if (reset)
      is_i <= 0;
    else begin
      is_i <= ~init ? (is_i + 1) : is_i;
    end
  end

  always_ff @(posedge reg_en, posedge reset) begin
     if(~reset) begin
       r1_out <= r1_out + r2_out + init_delay;
     end
     else
       r1_out <= 0;
   end
   
   always_ff @(posedge ~reg_en, posedge reset) begin
     if(~reset) begin
       r2_out <= r1_out + r2_out + init_delay;
     end
     else
       r2_out <= 0;
   end
   
   always_ff @(posedge clock, posedge reset) begin
     if(~reset) begin
       i_g_o <= i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0];
       init <= 0;
       win <= win | (~(i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0]) & (is_i == 8'b00011111));
       init_delay <= init;
     end
     else
       i_g_o <= 0;
       init <= 1;
       win <= 0;
       init_delay <= 0;
   end
   
   assign reg_en = init | is_i[2];

endmodule: datapath

ハードウェアなので、上から順番に実行ではなく、 assign で繋がれている所は代入元が変化したら代入先も常に変化する。 <= の代入も上から順番ではなく、上の @ のところに書かれているタイミング(posedge xx が0から1になった瞬間)に同時に起こる。

このプログラムが結局何をしているかというと、 reset が1のときに初期化し、以降は clock が変化する度に is_i をカウントアップし、 user_inis_i ビット目が {r2_out, r1_out}[is_i[2:0]] に等しいかを調べている。 r1_outr2_outreg_en が変化したタイミングで足しあわされる。

よって、次のPythonプログラムで、 user_in に入れるべき値が分かる。

answer = []

r1_out = 0
r2_out = 0
reg_en_prev = 1
init_delay = 1

for i in range(32):
    answer += [(r2_out<<4|r1_out)>>(i&7)&1]

    a_out = r1_out + r2_out + init_delay
    init_delay = 0
    reg_en = i>>2&1
    if reg_en_prev==0 and reg_en==1:
        r1_out = a_out
    if reg_en_prev==1 and reg_en==0:
        r2_out = a_out
    reg_en_prev = reg_en

print "".join(map(str, answer[::-1]))
01111100101110000001010000010000