pretty code

2023年12月29日 星期五

Some tips in Makefile - 002

01. export 

ABC ?= abc

上面表示如果 $(ABC) 未定義,則值等於 abc。

我們可以在呼叫 make 前,在 Shell 下 export ABC=ddd,則 Makefile 中就可以看到被定義成 ddd,另外一種方式則是 make ABC=ddd xxxx_target。

如果在 Makefile 中寫成 export ABC ?= abc,則這個 $(ABC) 可以傳遞到其他的程序。

假設是這樣執行 $(shell ./test.sh),即使用了 export,test.sh 中也看不到 $ABC 這個變數,這部份待確認?

順帶一提,在 Shell 下使用 export 則是將變數傳給其他的程序,比如另一個 shell script 或是執行檔等,像 C 語言可以用 getenv 取的環境變數。

02. 只看 make 執行過程,不真正執行

make -n 即可。

03. 使用 wildcard, abspath, notdir

像是在 Makefile 印出當前目錄下的所有檔案但不包括目錄本身,如果要絕對路徑則是用 abspath 取代 notdir 即可。

$(foreach log, $(notdir  $(wildcard ./*.log), $(info $(log)))

另外,foreach 語法如下:

$(foreach n, list, text)

list 是一串用空白分隔的字串,n 是當前取到的單一字串,text 則是你要做的事。

04. $@, $^, $<, $?, $*

有一 rule 如下:

main.o:    main.c def.h
            gcc -c main.c

假設 def.h 有更動過。

$@ = main.o  --> target
$^ = main.c  def.h  --> dependence
$< = main.c  --> first dependence
$? = def.h  --> changed file
$* = main  --> the primary name of target

故 gcc -c main.c  可以寫成 gcc -c $<。

另外,假設 target = all,則 $* 是空字串,因為 all 沒有副檔名。

2023年12月28日 星期四

Some tips in Makefile - 001

今天開始接觸未來可能的工作流程,雖然只是練習用,但也算是有點感覺了。

主管還很貼心的提供了一個小測驗,由於我對 Tcl 或是 Makefile 都只是略懂,一開始先花點時間整理脈絡,直到看到主管說的關鍵字後,便開始主攻這個測驗。

雖然這個測驗有點像腦筋急轉彎?但在一步一步分解問題後,最後還是能找到解法,但我也花了一個小時多有,還好在吃飯前順利搞定。

簡單快速紀錄一下,今天學到的東西XD

01. Makefile 變數賦值

= 在使用到變數時展開。
:= 宣告變數時就展開。
?= 變數為空時才給值。
+= 將值附加到原本變數。

02. .DEFAULT_GOAL

如題,此變數為 GNU make 的一個特殊變數,有設定這個,假設 make 後面沒帶 target 就會以這個變數為主,而不是檔案中找到的第一個 target。

03. $$0 in awk command of Makefile

原本 awk 取原始字串是 $0,但在 Makefile 中 $ 有特殊用途,故在 awk 指令中,需要多加一個 $。

另外,如果想印出 $@,則應該要這樣寫 @echo \$$@。

04. env variable in Tcl

puts $::env(OS),此指令的意思是印出 OS 這個環境變數,env 本身是 array,array in Tcl 是關聯陣列。

05. call proc in some Tcl files.

source $::env(SCRIPTS_DIR)/report_metrics.tcl
report_metrics 6 "finish"

report_metrics.tcl 檔案中都是函數,這樣就可以呼叫該檔案裡面的 report_metrics 函數。

06. /usr/bin/time -f '%E %MKb' sleep 5

time 是用來記錄執行指令的時間,不同的 format 可以顯示不同的資訊,上面表示記錄 sleep 5 執行的時間及最大記憶體使用。

07. export QT_QPA_PLATFORM = offscreen,console 執行時就不會報錯。

08. $(shell command -v ls)

執行 command 查詢 ls 指令。

09. awk, mawk, gawk 三個是不同的程式

awk 'match($$0, pattern, array) 是錯的,因為這是 gawk 才有的語法,awk 有 match 但不能將 pattern 裡面抓到的 group 資訊存到 array。

2023年12月27日 星期三

iverilog + GTKWave 使用小技巧


這應該是我看過寫得最好的文章了。

除了教你如何 dump array,也教你如何儲存設定好的波形檔案(GTKWave -> File -> Write Save File As,假設檔名為 config.gtkw),下次啟動 GTKWave 就不用重拉了(gtkwave config.gtkw),這應該更有助於 debug。

後悔沒早看到這篇文章,只來得及儲存除法流水線範例設定檔。

2023年12月26日 星期二

Display verilog array built by iverilog in GTKWave

Ans:使用 generate 語法即可,generate for block 要不要命名都可以,這應該是目前最方便的 workaround 了。

`timescale 1ns/1ns
`define VIEW 1

module test;
    initial begin
        if (`VIEW == 1) begin
            $dumpfile("wave.vcd");
            $dumpvars(0, test);
        end
    end

    reg [3:0]  buffer[0:7];

    genvar j;
    generate
        for (j = 0; j < 8; j = j + 1) begin: buffer_array
            wire [3:0] buff;
            assign buff = buffer[j];
        end
    endgenerate

    integer i;
    initial begin
        for (i = 0; i < 8; i = i + 1) begin
            #10;
            buffer[i] = i[3:0];        
        end
        
        #200;
        $finish;    
    end
    
endmodule


2023/12/27 更新

還有另一個作法是使用 $dumpvars,這個似乎是 iverilog 作者在回網友郵件中建議的?這個語法更少,雖然 vpp 模擬時會有 warning,不過在 GTKwave 中不會多一個臨時變數,看起來更直觀,但似乎不是都可以用?請詳此處

module test;
    initial begin
        if (`VIEW == 1) begin
            $dumpfile("wave.vcd");
            $dumpvars(0, test);
            for (i = 0; i < 8; i = i + 1) begin
$dumpvars(1, test.buffer[i]);
    end
        end
    end

    reg [3:0]  buffer[0:7];

timescale in iverilog compiler

下午看乘法器流水線範例時,在看懂了 wave 後,想要印出各個實例化的模組是否在我想的時間內進去主要的計算區塊(mult_cell.v, en == 1),於是便多傳了一個 id 進去並在 en == 1 時,使用 $display($time, " ", id); 來印出目前時間。

沒想到不管怎樣 $time 永遠印出 0,明明 wave 都是間隔 1 個 cycle 時 rdy 才等於 1?

想了一個下午,終於在 5 點多想到原因。

timescale 是 compiler directive,可以在 Standard 2001 Section 19 看到相關說明。

顧名思義,這個指令是在編譯階段決定,由於這個範例有 3 支檔案,沒辦法用我之前的 Windows batch 編譯,只能手動打指令編譯,但我在呼叫 iverilog 時,沒有將 Testbench 擺在最前面,導致 iverilog 在編譯 cell module 或 man module 時,因為未看到 timescale 指令,故無法正確計算 $time。

下次一定要記得將 Testbench 擺在第一個。

如果當初 iverilog 後有帶 -W all,此時也會看到相關警告訊息。

D:\Verilog\05_manual_build>iverilog -W all -o wave mult_cell.v mult_man.v mult_tb.v
mult_tb.v:76: warning: implicit definition of wire 'rstn'.
warning: Some modules have no timescale. This may cause
       : confusing timing results.      Affected modules are:
       :   -- module mult_cell declared here: mult_cell.v:1
       :   -- module mult_man declared here: mult_man.v:1

修正後終於可以驗證我的想法對不對了。


雖然將 -W all 打開,可以避免這個失誤,但會多出很多不必要的警告訊息。

之後也許可以參考這裡,只設定必要的 Warning 即可。

-Wanachronisms
-Wimplicit
-Wimplicit-dimensions
-Wmacro-replacement
-Wportbind
-Wselect-range
-Wtimescale
-Wsensitivity-entire-array

2023年12月25日 星期一

Verilog 非阻塞賦值

雖然知道這個概念,但每次看 wave 時都會中招XD

reg [3:0] a = 1,  b = 0;
always @(posedge clk) begin
    if (a != 0) 
        a <= a + 1    
end

always @(posedge clk) begin
    if (a == 1)
        b <=  1
end

假設兩個 always block 第一次 if 那個時間點是 10 ns,在此當下 a = 1, b = 0,因為電路是並行緣故。

但因為 wave 在此點後都會顯示新的值,故常會認為該點也是新的值!

我想我還需要一點時間來習慣,這個非流水線乘法器是個不錯的例子,忘記可以回來複習。

另外,關於如何在白紙上計算驗證也是我現在頭痛的問題,寫的真亂。


65 ns 時,因為 data_rdy = 1,故會進到 cnt 與 cnt_temp 互相作用的那個 always block,但因為電路並行,故 cnt_temp 值還未反映到 cnt,因此 mult1 和 mult2 那個 always block if 判斷都要看非阻塞賦值前的值,同理兩個 shift 變數也要以非阻塞賦值前的值來做計算。

2023年12月18日 星期一

學習 Verilog 前應該要知道的事

Hardware Description Language (HDL) 是硬體描述語言,我們比較常聽到的是 VHDL 和 Verilog。

因為是描述語言,重點是描述硬體行為,故在學習時不能用軟體程式語言的思維去理解它,比如硬體電路是並行的,不像一般循序程式語法有先後順序概念(雖然在 Testbench 中可以使用 delay 或阻塞賦值確保順序)。

Verilog 共有 3 個標準,我們比較常用的應該是 Verilog-2001。

Verilog 1364 - 1995 (Verilog - 95)
Verilog 1364 - 2001 (Verilog - 2001)
Verilog 1364 - 2005 (Verilog - 2005)

SystemVerilog 則是為了補充 VHDL 和 Verilog 的不足,有點像是他們的超集合。

SystemVerilog 除了是 HDL 也是 Hardware Verification Language。

其實還有很多,後續再慢慢補充吧…

2023年12月16日 星期六

Simulation and Synthesis

雖然還不到那個階段,但近廟懂拜佛,常聽其他同事提到這兩個名詞。

這篇文章看到,雖然 Verilog Language 可以用在這兩個地方,但不是每個 Verilog 語法都能被綜合,像是 tasks, delays, events, fork..join, initial..begin blocks, force and release statements.

另外還有個名詞叫 Post-Synthesis Simulation,雖然也是 Simulation,但看的是 synthesized 後的結果。

目前我離那邊應該還有段距離吧。

@ in Verilog


可能我學習 Verilog 的網頁寫的不好?或是 @ 概念是在做板子測試時看到故沒有放到心裡去!

昨天下午在學 repeat 語法時,該範例的波形讓我一直百思不得其解,終於在睡覺前大概知道為什麼,但就差在不確定 @ 是否有等待的作用?

雖然從結果來看,就像是 wait?

下午外出回來洗完澡,用 "Verilog @ syntax" 關鍵字查詢,終於看到一個大學的 PDF 有描述到這個語法,但我還是想要看到更官方的資料(IEEE?)。

總之,我昨天犯了一個錯誤:

明明告訴你是 posedge clk,故接下來的 code 理論上應該已經離開了 posedge clk 那個點,先不論 @ 是否會 wait,本來就應該要到下一個 poedge clk 才會再被偵測到,也就是下一個 clock cycle。

所以 96 ns 時,雖然 j = 0,但要到下一個 clock,106 ns 時才會對 buffer0 設值。

我們可以簡單用這個小程式來觀察時間點,從時間單位 10 開始,每隔 20 單位依序印出 1 和 2 當下的時間。

reg clk = 0;

always begin
   #10;
   clk = ~clk;
end

always @ (posedge clk) begin
   $display("1", $time);
   @(posedge clk) begin
       $display("2", $time);
   end
end

整個 repeat example 如下:

`timescale 1ns/1ns

module study;
   initial begin
      $dumpfile("wave.vcd");
      $dumpvars(0, study);
   end

   reg [3:0]    counter3;
   initial begin
      counter3 = 'b0;
      repeat (11) begin
         #10;
         counter3 = counter3 + 1'b1;
      end
   end

   //(3)
   reg          clk;
   reg          rstn;
   reg          enable;
   reg [3:0]    buffer[7:0];
   reg [3:0]    buffer7, buffer6, buffer5, buffer4, buffer3, buffer2, buffer1, buffer0;
   integer      j;
   integer      i = 0;

   initial begin
      clk       = 0;
      rstn      = 1;
      enable    = 0;
      #3;
      rstn      = 0;
      #3;
      rstn      = 1;
      enable    = 1;
      forever begin
         clk <= ~clk;
         #5;
      end
   end

   always @(posedge clk or negedge rstn) begin
      j = 0;
      if (!rstn) begin
         repeat (8) begin
            buffer[j] <= 'b0;
            j = j + 1;
         end
         i = 1;
      end
      else if (enable) begin
         repeat (8) begin
            @(posedge clk) buffer[j] <= counter3;
            j = j + 1;
         end
      end
   end

   always @(buffer[7]) begin
        buffer7 <= buffer[7];
   end

   always @(buffer[6]) begin
        buffer6 <= buffer[6];
   end

   always @(buffer[5]) begin
        buffer5 <= buffer[5];
   end

   always @(buffer[4]) begin
        buffer4 <= buffer[4];
   end

   always @(buffer[3]) begin
        buffer3 <= buffer[3];
   end

   always @(buffer[2]) begin
        buffer2 <= buffer[2];
   end

   always @(buffer[1]) begin
        buffer1 <= buffer[1];
   end

   always @(buffer[0]) begin
        buffer0 <= buffer[0];
   end

   //stop the simulation
   always begin
      #10;
      if ($time >= 1000) $finish;
   end

endmodule

上面 buffer0 ~ buffer7 是為了接住 buffer[7:0] array 值用,可能是我還不會用 iverilog,無法在 GTKWave 中看到 array,故先用這種方式方便我看波形。

另一個很重要的概念是 = 雖然是阻塞賦值,可以控制流程先後順序,但這個操作應該是不佔時間,至少我從 wave 中看到,變數 i 的值是在 3 ns 時變成 1。

2023/12/18 更新

這應該是 Verilog - 2001 Standard 裡面關於 @ 的描述。


2023/12/26 更新

可以用 generate 語法來 mapping array,比宣告 buffer1 ~ buffer7 還方便。
buffer[0] 就是 test.array_buffer[0].buf。

genvar k;
generate
    for (k = 0; k < 8; k = k + 1) begin: array_buffer
    wire [3:0] buf;
    assign buf = buffer[k];
    end
endgenerate

2023年12月15日 星期五

A windows batch file of compiling verilog in UltraEdit

老樣子,簡單的程式直接使用 UltraEdit 編輯及編譯,環境設定好之後,學習 Verilog 應該會更有效率。

一個簡單的 Verilog Testbench 不一定要實體化某個 module,如果只是使用 $display or $monitor 顯示一些訊息也不一定要產生 VCD 檔案。

使用方式

使用 UltraEdit 開啟任意一個 TestBench 檔案,檔名格式為 XXX_tb.v
副檔名不一定要 .v,只是一般習慣這樣命名。

最重要的是 %p %n %e 都用小寫,大寫是 "8.3" short filename for DOS(用了快 20 年的 UltraEdit,我也是直到今天才知道XD)。


我的 Windows Batch 會去做上述檢查並決定是否要呼叫 GTKWave。

Windows Batch

@echo off

SET OUTPUT=wave
SET OUTPUT_VCD=wave.vcd

IF EXIST "%OUTPUT%" (
    del /Q %OUTPUT%
)

IF EXIST "%OUTPUT_VCD%" (
    del /Q %OUTPUT_VCD%
)

SET MODULE_TB=%1
SET MODULE_TMP=%MODULE_TB:_tb=%

echo %MODULE_TB%
echo %MODULE_TMP%
echo.

IF EXIST %MODULE_TMP% (
    SET MODULE=%MODULE_TMP%
)

iverilog -o %OUTPUT% %MODULE_TB% %MODULE%

IF NOT "%ERRORLEVEL%" == "0" (
    echo.
    pause
    goto END
)

vvp -n %OUTPUT% -lxt2

IF NOT EXIST %OUTPUT_VCD% (
    goto END
)

start gtkwave %OUTPUT_VCD%

:END
echo.

The Verilog syntax configuration of UltraEdit

/L16"Verilog 1364-2005" Line Comment = // Block Comment On = /* Block Comment Off = */ Block Comment On Alt = /* Block Comment Off Alt = */ String Chars = " File Extensions = V VH VL VMD
/Colors = 0,8421376,8421376,8421504,255,
/Colors Back = 16777215,16777215,16777215,16777215,16777215,
/Colors Auto Back = 1,1,1,1,1,
/Font Style = 0,0,0,0,0,
/Delimiters = ~!@%^&*()-+=|\/{}[]:;"<> , #
/Function String = "%[ ^t]++^(config[ ^t^p]+[a-zA-Z0-9_]+^)"
/Function String 1 = "%[ ^t]++^(module[ ^t^p]+[a-zA-Z0-9_]+^)[ ^t^p]++[(;#]"
/Function String 2 = "%[ ^t]++^(task[ ^t^p]+[~(;]+^)[ ^t^p]++[(;#]"
/Function String 3 = "%[ ^t]++^(function[ ^t^p]+[~(;]+^)[ ^t^p]++[(;#]"
/Function String 4 = "%[ ^t]++^(primitive[ ^t^p]+[~(;]+^)[ ^t^p]++[(;#]"
/Function String 5 = "begin[ ^t^p]++^(:[ ^t^p]++[a-zA-Z0-9_]+^)"
/Indent Strings = "begin" "case" "fork" "specify" "table" "config"
/Unindent Strings = "end" "endcase" "join" "endspecify" "endtable" "endconfig"
/Open Fold Strings = "module" "task" "function" "generate" "primitive" "begin" "case" "fork" "specify" "table" "config" "`ifdef" "`ifndef" "`else" "`elsif" "`celldefine" "`protect" "`protected"
/Close Fold Strings = "endmodule" "endtask" "endfunction" "endgenerate" "endprimitive" "end" "endcase" "join" "endspecify" "endtable" "endconfig" "`endif" "`endif" "`endif" "`endif" "`endcelldefine" "`endprotect" "`endprotected"
/Open Brace Strings = "{" "(" "["
/Close Brace Strings = "}" ")" "]"
/C1"Keywords" Colors = 16711680 Colors Back = 16777215 Colors Auto Back = 1 Font Style = 0
always and assign automatic
begin buf bufif0 bufif1
case casex casez cell cmos config
deassign default defparam design disable
edge else end endcase endconfig endfunction endgenerate endmodule endprimitive endspecify endtable endtask event
for force forever fork function
generate genvar
highz0 highz1
if ifnone incdir include initial inout input instance integer
join
large liblist library localparam
macromodule medium module
nand negedge nmos nor noshowcancelled not notif0 notif1
or output
parameter pmos posedge primitive pull0 pull1 pulldown pullup pulsestyle_onevent pulsestyle_ondetect
rcmos real realtime reg release repeat rnmos rpmos rtran rtranif0 rtranif1
scalared showcancelled signed small specify specparam strong0 strong1 supply0 supply1
table task time tran tranif0 tranif1 tri tri0 tri1 triand trior trireg
unsigned use uwire
vectored
wait wand weak0 weak1 while wire wor
xnor xor
/C2"System" Colors = 255 Colors Back = 16777215 Colors Auto Back = 1 Font Style = 0
** .
** 'b 'B 'o 'O 'd 'D 'h 'H 'sb 'sB 'so 'sO 'sd 'sD 'sh 'sH 'Sb 'SB 'So 'SO 'Sd 'SD 'Sh 'SH
** $
/C3"Operators" Colors = 33023 Colors Back = 16777215 Colors Auto Back = 1 Font Style = 0
!
%
&
*
+
,
-
// /
:
;
<
=
>
?
@
^
{
|
}
~
/C4"Directives" Colors = 32768 Colors Back = 16777215 Colors Auto Back = 1 Font Style = 0
** `
/C5"DelaysParametersEscaped" Colors = 4210816 Colors Back = 16777215 Colors Auto Back = 1 Font Style = 0
#
** \

2023年12月14日 星期四

第一支 Verilog 程式

上星期四就已經將交接事項全部完成,但臨時被交辦要拿一塊真實板子將某個專案流程實際跑過一遍,板子配 DRAM 問題總是千奇百怪,這也是我最擔心的部分,故押了一個兩個星期後的完成日期。

因為 A chip 板子不能開機,趁著同事如火如荼 debug 時,改拿 B chip 板子來跑,雖然收集資料階段可以跑完,但也是死當在我的最後一個 UEFI script 開機途中,本想忽略這個狀況繼續往下走,沒想到連開機值的 MT86 測試都過不了,害我浪費了不少時間。

昨天晚上拿到同事處理好的 A chip 板子,但因為是 debug 版 BIOS,開機需花不少時間,終於在今天中午時順利地進到我們專案的最主要測試流程。

上星期四到今早都只能斷斷續續的學語法,還要分心注意板子,進度幾乎為 0!下午總算可以好好學 Verilog 了。

花點時間思考一下,參考網友文章,選擇使用 Icarus Verilog 來 compile 並用 GTKWave 來看波形,雖然功能不多,但目前夠我用了。

iverilog - compile verilog code.
vvp - simulation engine.
gtkwave - wave viewr.

編譯方式

@echo off

REM compile code.
iverilog -o wave led_demo.v led_demo_tb.v

IF NOT "%ERRORLEVEL%" == "0" (
    echo.
    pause
    goto END
)

REM convert wave to wave.vcd
vvp -n wave -lxt2

REM run gtkwave to check signals.
gtkwave wave.vcd


:END
echo.

Testbench

`timescale 1ns/100ps

module led_demo_tb;

parameter SYSCLK_PERIOD = 10;

reg SYSCLK;
reg NSYSRESET;

initial
begin
    SYSCLK = 1'b0;
    NSYSRESET = 1'b0;
end

// For Iverilog
initial
begin
    $dumpfile("wave.vcd");
    $dumpvars(0, led_demo_tb);
end

initial
begin
    #(SYSCLK_PERIOD * 10)
        NSYSRESET = 1'b1;
    #1000
        $stop;
end

always @ (SYSCLK)
    #(SYSCLK_PERIOD / 2.0) SYSCLK <= !SYSCLK;

led_demo led_demo_ut0 (
    // Inputs
    .rst_n(NSYSRESET),
    .clk(SYSCLK),

    // Outputs
    .led(led)
);

endmodule

LED Module

module led_demo(
    input clk,
    input rst_n,

    output reg led
);

reg [4:0] cnt;

always @ (posedge clk)
begin
    if (!rst_n)
        cnt <= 0;
    else if (cnt >= 10)
        cnt <= 0;
    else
        cnt <= cnt + 1;
end

always @ (posedge clk)
begin
    if (!rst_n)
        led <= 0;
    else if (cnt == 10)
        led <= !led;
end

endmodule

GTKWave 畫面


該網友的範例是個很不錯的 Hello World,包含很多基礎語法。

如果只是要學 Verilog 語法,也可以單寫 Testbench 就好。 

總之,Verilog 一定要配合波形看(simulation),學習才能事半功倍。

2023年12月11日 星期一

買新不買舊

雖然我後來為了精度選擇購買 REVOPOINT MINI,但如果讓我再選擇一次,我應該會改買 POP3!

REVOPOINT POP3 是今年推出的機種,本身有內建陀螺儀加上購買豪華版有附贈手機用的移動電源套組,整體使用上會比較通用。

POP3 光掃描範圍就比 MINI 大上一些,配合旋轉盤的標誌點,比較能夠對付一些沒有顯著特徵點的物件。

但我也沒有後悔選擇購買 MINI 就是,畢竟要作比較當然要跟最好的機型比,這樣也比較知道我們跟消費型機種 3D 結構光掃描儀的真實差距!

附上一張兩者的比較圖(多放上 RANGE,此機器是掃描大物件用)。


MINI 放在桌上的大小示意圖。

2023年12月6日 星期三

離職交接真是門大學問

最近最讓我頭痛的一個交接項目是我剛來這家公司參與的專案,這個專案的目的是要管理 Android based 設備,因為 OTA 等特殊操作緣故,我們的 app 需要 sign key。除了 Client 端,我們 Server 端將 API 與 DB API 切開,故整體又分為兩大 Server。

當初我負責的是 DB 部份,一開始只會寫 C 和把 PHP 當 script 的我,對 Web 架構並沒有那樣熟悉,故使用 C 來寫 Apache 的 plug-in,中間使用 FastCGI protocol 來溝通,這樣的好處是 Web Request 流量可以由 Apache 來承擔,只要頭過身就過?當然我自己本身的處理能力要夠快,這部份的瓶頸一開始只會出現在 DB 端,只要 device 數量不要太誇張,理論上都沒什麼問題。印象當初透過模擬器測試,1000 ~ 2000 台是沒什麼大問題,除了上下載的檔案不能過大否則容易 timeout。

因為最新 Android 版本和當初開發版本已經離很遠了,有些 API 或權限改變不能用那都是可預期的,再加上現在都是用 Android Studio 當開發環境,故這個專案交接其實只要知道 Code 跟文件放在哪裡就好,坦白說這很合理,這個專案應該不會再有重見天日的一天XD

但既然要交接,想說至少讓 Server 端拉最新 Code 下來,配合現在 Node.js 等版本,讓他可以無痛跑起來,避免以後真的要用沒人可以處理,這樣也算是我的小小心意。

沒想到就是這個決定,害我邁向偉大航道的計劃一直無法順利展開,假日除了忙家人的事、土炮結構光比較外,其餘時間都在想怎樣讓 Server 在新的開發環境版本下能夠繼續存活,避免改動更多的 Code。

Web Server

這部份是當初前同事寫的,完全都用 Node.js 開發,但他當初離職交接文件有註明:因為要混淆 code 關係,Node.js 只能用很舊的版本(6.X -> 0.12),而目前最新 LTS 版本是 20.10.0。從這可以看出這中間變化一定不小,為了更貼近現況,我決定使用我目前手上的版本 18.X,這是兩年內的版本,還算可以接受,理論上應該要跳到最新版本,但顧慮到其他人手上版本大概也是介於 18.X 前後,便決定使用這個版本。

前同事很貼心的寫了批次檔可以一鍵安裝,但安裝後一直無法順利執行,原本以為是同事離職文件提到的 npm issue,在多次安裝後依然無法解決。後來靜下心來一看,錯誤雖然是說找不到模組,但確實是有安裝成功,還好在經歷了之前幾個交接項目的荼毒,認定是版本問題,去 N 年前的開發資料夾找到版本號並更新到 package.json,大概改動了幾項,執行便不再有問題。故問題的本質是不要用 * 號當版本號。

其實這會有資安問題,但除非一直用最新版本 daily build 每日做檢查,不然隔一段時間一次進最新版的問題會讓你想哭,由於我沒待過像亞馬遜那樣的大公司,我本身也不是寫 Node.js 的,目前還沒想到怎樣的方式才是最好的?

Device Simulator

搞定 Server 後,雖然已經沒有當初的 Android device 可以跑,但 N 年前我寫了一個可以模擬多台 device 的模擬器,雖然我只實作測試 peformance 需要的指令,但拿來驗證 Server 綽綽有餘!

無奈 socket.io 一直斷線,依稀記得似乎是兩邊的版本要一致?老樣子,找到當初 socket.io 相關模組用版本號,同時更新 Server 及 Simulator package 後,這部份就搞定了。

Record Server

坦白說在搞定上面兩項後,我已經一夜白髮了XD

但路都走到這裡了,還是想完成當初內心承諾,於是再接再勵的勇往直前!原本以為這是最簡單的,畢竟是當初自己開發的,沒想到安裝和設定雖然簡單,但透過 UI 設定 Record Server 的最後步驟一直無法成功?

雖然我都有 Log,但全部架構已經遺忘的差不多了,回頭看文件也是一頭霧水,故一時間也看不出所以然,再加上之前判斷錯誤那一行寫的不嚴謹,多往下做了幾個無謂動作,最後將 Log filter 開到最細並且回過頭來從 request 端看到出問題地方的 code,才讓我發現問題不是出在接收端,而是在我必須發 API 給 Web Server 取 token 那端發生錯誤,由於我使用的是很有名的 libcurl 函式庫,故用我當初最粗 Log filter 的關鍵字就可以 google了,而這個錯誤就是 error code 35。

A problem occurred somewhere in the SSL/TLS handshake. You really want the error buffer and read the message there as it pinpoints the problem slightly more. Could be certificates (file formats, paths, permissions), passwords, and others.

從官方文件可以得知問題是出在 HTTPS 握手階段,只可惜雖然我有將 libcurl debug mode 打開,但並未看到更多錯誤訊息。

我依稀聽過這似乎是 HTTPS 安全性政策更新造成的問題,最初的 SSL 早就被禁用,還好我當初就有留指定 TLS 版本的 code,將註解打開並重新編譯執行檔即可。但不論我指定 1.1 還是 1.2,error code 35 依舊發生?

這不禁讓我懷疑是不是又是 Web Server 某些模組版本需要調整?思考了一下,還是看看之前自己來公司到現在寫的測試資料夾裡面是不是有什麼可以快速發 Web Request 的 code 來利用,我應該至少要有 Node.js、Python、Golang 的資料夾可以搜尋?

剛好第一個找到的就是 Node.js example,用的又是內建模組 https,完全是我需要的,這樣可以先排除版本問題XD

不試不知道,Web Server 居然有收到 API?但我還是很不敢相信,還好當初這個專案開發完後,有鑒於用 C 開發 Web Service 真是自討苦吃,故當初有稍微自學一下 Golang,並稍微的開發 Golang 版的 Record Server,雖然只實作了 3 個 API,但剛好也是 UI 打過來的 API,拿來驗證也是綽綽有餘,最後發現我的 Golang 版 Server 也是可以順利的發 API 給 Web Server。

事已至此,我也認命了,一定是 libcurl 的問題,雖然我不知道為什麼 TLS 1.2 會有問題就是?

之前用了好幾個第三方 C/C++ 模組,除了一個是 C++ Template 外,每個都要用我慣用的 GCC 重新編譯。

本想直接使用 vcpkg + VC 來搞定,但鑒於之前的使用經驗,我也不會期待可以一次搞定!幸好天公疼憨人,想到去年當 C++ 講師時,為了讓學員最後一堂課作業方便使用,當初網路爬蟲沒有使用 boost 而仍舊使用 libcurl,記得當初所用版本打開 TLS 1.2 是正常的?

最後直接使用官方下載的最新編譯好版本(8.4.0_9)配合我舊版的 TDM-gcc(9.2.0)也是無痛接軌,且此版本似乎不再依賴 openssl?至少我只有 link libcurl.dll.a 加上執行時期的 libcurl.dll 便可以執行 HTTPS request。

終於整體 Server 架構都已搞定,看著訊息打來打去,DB 也如預期的記錄系統使用率歷史資料,今天只要順利跑過一整天測試,我終於可以完成最後一項交接項目了!

後記

一來辦公室就開始壓力測試,昨天下班前無聊順手點了一下 screenshot 指令,device simulator 居然會 crash,雖然多做了錯誤檢查避開這個奇怪錯誤,但想說還是在壓力測試前試著搞定本來可以用的指令?還好昨天洗澡時就覺得這個問題好像跟兩年前幫另一個同事解決 issue 的狀況很像,都是 multiparty form-data empy?

邊喝咖啡邊看之前的 Tech-Note,看來是 Node.js 14 開始,某些平台 stream 會有問題,指定 multiparty = 4.2.2 即可。

今天終於可以輕鬆一下了,只要測試沒有其他大問題,我可以慢慢地為這最後一個交接項目收尾了,一切真是得來不易呀,畢竟也從上星期開始搞到今天XD

放張多台 device screenshot command result 圖留作紀念。


下午還是花了點時間確認一下 openssl 版本,如下圖所示,1.0.1 才算正式支援。我 N 年前使用的 libcurl link 的是 1.0.0o,這也是為什麼不支援 TLS 1.2 的真正原因。


要下班了,留個記錄先,看起來大部份元件有定期回收機制,故會重啟 process,目前只有我的 Record App 和 node.exe 記憶體增加了,但應該尚屬合理,只有大量且多日的壓力測試才能做更進一步的分析。


2023/12/08 更新

前天電腦沒關機讓他又跑了一天,直到昨天 7 點多下班才關掉所有測試,留下最後記錄XD