""" Parse QAQC JATHub slave log. See [`parse_slavelog_file`](@ref) for the main function. """ module SlaveLogParser using StaticArrays using Printf using AutoHashEquals const HEADER_QSPIP_START = "=============== Test QAPIp Start ===============" const HEADER_POWER_START = "=============== Test Power Start ===============" const HEADER_ASDTP_START = "=============== Test ASDTP Start ===============" const HEADER_RECOV_START = "=============== Test Recov Start ===============" HEADER_STARTS = SVector(HEADER_QSPIP_START, HEADER_POWER_START, HEADER_ASDTP_START, HEADER_RECOV_START) """ Indicate parser state. Default is `MODE_NONE`. In `MODE_NONE`, each line is fed into parser to detect the start of each section. """ @enum SlaveLogSection begin MODE_NONE MODE_QSPIP MODE_POWER MODE_ASDTP MODE_RECOV end struct SlaveLogResult end """ get_psbid_runid_from_filename(filename::AbstractString)::Tuple{Int64,Int64,Bool} Extract info from slave log filename. Returns a tuple of `psbid`, `runid`, `islongrun`. """ function get_psbid_runid_from_filename(filename::AbstractString)::Tuple{Int64, Int64, Bool} main, _ext = splitext(filename) parts = split(main, '_') psbid = parse(Int64, parts[1]) runid = parse(Int64, parts[2]) islongrun = if length(parts) == 3 true else false end (psbid, runid, islongrun) end function is_valid_slavelog(filename::AbstractString)::Bool error("not yet implemented") end """ detect_mode_start(line::AbstractString) Detect [`SlaveLogSection`](@ref) from section starting header line. If the line doesn't match any section, returns `nothing`. """ function detect_mode_start(line::AbstractString) if line == HEADER_QSPIP_START MODE_QSPIP elseif line == HEADER_POWER_START MODE_POWER elseif line == HEADER_ASDTP_START MODE_ASDTP elseif line == HEADER_RECOV_START MODE_RECOV else nothing end end """ detect_mode_start!(mode::SlaveLogSection, line::AbstractString) Detect mode from the `line` and update `mode`. """ function detect_mode_start(mode::SlaveLogSection, line::AbstractString) newmode = detect_mode_start(line) if !isnothing(newmode) mode = newmode end mode end """ parse_qspip_section(lines::Base.Iterators.Stateful) Parse QSPIp section of given stateful iterator of log. # Args - `lines`: Stateful iterator of slave log file lines """ function parse_qspip_section!(lines::Base.Iterators.Stateful) # TODO nothing end # ================================== # Power """ Results from power test # Fields - result_3v3d::Float64 - result_3v3a::Float64 - result_n3va::Float64 - fpga_temp::Float64 - channelvals::SVector{16, @NamedTuple{dac::Int64, adc::Int64}} - result::Bool """ struct PowerResult result_3v3d::Float64 result_3v3a::Float64 result_n3va::Float64 fpga_temp::Float64 channelvals::SVector{16, @NamedTuple{dac::Int64, adc::Int64}} result::Bool end """ parse_power_section!(lines::Base.Iterators.Stateful)::PowerResult Parse Power section of given stateful iterator of log. # Args - `lines`: Stateful iterator of slave log file lines """ function parse_power_section!(lines::Base.Iterators.Stateful)::PowerResult line = popfirst!(lines) result_3v3d = let m = match(r"^3V3D \[V\] = ([\d|\.]+)$", line) parse(Float64, m[1]) end line = popfirst!(lines) result_3v3a = let m = match(r"^3V3A \[V\] = ([\d|\.]+)$", line) parse(Float64, m[1]) end line = popfirst!(lines) result_n3va = let m = match(r"^-3VA \[V\] = ([-|\d|\.]+)$", line) parse(Float64, m[1]) end line = popfirst!(lines) fpga_temp = let # can be minus (see 127_172.txt) m = match(r"^FPGA Temprature \[C\] = (-?[\d|\.]+)$", line) parse(Float64, m[1]) end line = popfirst!(lines) line = popfirst!(lines) channelvals = Iterators.map(1:16) do ch ch_s = @sprintf "%x" (ch - 1) re = Regex("channel $(ch_s): DAC \\[mV\\] = (\\d+), ADC \\[mV\\] = (\\d+)\$") line = popfirst!(lines) # I'm not sure this mutating operation is called in sequence m = match(re, line) (dac = parse(Int64, m[1]), adc = parse(Int64, m[2])) end |> Tuple |> SVector{16, @NamedTuple{dac::Int64, adc::Int64}} line = popfirst!(lines) result = let m = match(r"^Test Power Reseult = (\d)$", line) if m[1] == "1" true else false end end PowerResult(result_3v3d, result_3v3a, result_n3va, fpga_temp, channelvals, result) end # ================================== # Asdtp """ Measurement result for asic in asdtp test. """ @auto_hash_equals struct AsdtpMeasurement before::Float64 current::Float64 next::Float64 end AsdtpMeasurement(x::NTuple{3, <:Real}) = AsdtpMeasurement(x...) function Base.parse(::Type{AsdtpMeasurement}, s::AbstractString) v = split(s, ':') @assert length(v) == 3 AsdtpMeasurement(parse(Float64, v[1]), parse(Float64, v[2]), parse(Float64, v[3])) end @auto_hash_equals struct AsdtpResult reconfig_done::Int64 always_hit_flag::Int64 autoreconfig::Union{Bool, Missing} asdtp_main::Union{Vector{Vector{AsdtpMeasurement}}, Missing} asdtp_reset::Int64 asdtp_total::Int64 reconfig_done_2::Int64 always_hit_flag_2::Int64 si_done::UInt64 lolb_in::UInt64 ppconfig_done::UInt64 ppconfig_error::UInt64 pllld_fail_counter::UInt64 ppconfig_fail_counter::UInt64 pp_pllds::MVector{8, UInt32} end """ parse_asdtp_section!(lines::Base.Iterators.Stateful)::AsdtpResult Parse ASDTP section of given stateful iterator of log. # Args - `lines`: Stateful iterator of slave log file lines """ function parse_asdtp_section!(lines::Base.Iterators.Stateful)::AsdtpResult line = popfirst!(lines) result_reconfig_done = let m = match(r"^reconfig_done = (\d+)$", line) parse(Int64, m[1]) end line = popfirst!(lines) result_always_hit_flag = let m = match(r"^always_hit_flag = (\d+)$", line) parse(Int64, m[1]) end line = popfirst!(lines) result_autoreconfig = if line == "Autoreconfig done" true elseif line == "Autoreconfig fail" false else missing end result_asdtp_main = if !ismissing(result_autoreconfig) && result_autoreconfig line_count = 0 line_count += 1 results = map(_ -> AsdtpMeasurement[], 1:8) for asic_id in 1:8 header_line = "----PP$(asic_id)----" while line != header_line line = popfirst!(lines) line_count += 1 end for _ in 1:32 line = popfirst!(lines) line_count += 1 mes = parse(AsdtpMeasurement, line) push!(results[asic_id], mes) end end @assert length(results[1]) == 32 "unexpected length: $(length(results[1]))" line = popfirst!(lines) @assert line == "100" results else missing end line = popfirst!(lines) result_asdtp_reset, result_asdtp_total = let m = match(r"^ASDTP : (\d+) times reset : result = (\d+)$", line) parse(Int64, m[1]), parse(Int64, m[2]) end line = popfirst!(lines) result_reconfig_done_2 = let m = match(r"^reconfig_done = (\d+)$", line) parse(Int64, m[1]) end line = popfirst!(lines) result_always_hit_flag_2 = let m = match(r"^always_hit_flag = (\d+)$", line) parse(Int64, m[1]) end line = popfirst!(lines) @assert line == "" line = popfirst!(lines) @assert line == "------- Done check -------" "actual line: $line" line = popfirst!(lines) result_si_done = let m = match(r"^Si_done = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end line = popfirst!(lines) result_lolb_in = let m = match(r"^LOLB_in = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end line = popfirst!(lines) result_ppconfig_done = let m = match(r"^PPconfig_done = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end line = popfirst!(lines) result_ppconfig_error = let m = match(r"^PPconfig_error = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end line = popfirst!(lines) result_pllld_fail_counter = let m = match(r"^PLLLD_fail_counter = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end line = popfirst!(lines) result_ppconfig_fail_counter = let m = match(r"^PPconfig_fail_counter = (0x[[:xdigit:]]+)$", line) parse(UInt64, m[1]) end result_pp_pllds = MVector{8, UInt32}(undef) for ppid in 1:8 line = popfirst!(lines) m = match(Regex("^PP$(ppid)_PLLLD = (0x[[:xdigit:]]+)\$"), line) result_pp_pllds[ppid] = parse(UInt32, m[1]) end AsdtpResult( result_reconfig_done, result_always_hit_flag, result_autoreconfig, result_asdtp_main, result_asdtp_reset, result_asdtp_total, result_reconfig_done_2, result_always_hit_flag_2, result_si_done, result_lolb_in, result_ppconfig_done, result_ppconfig_error, result_pllld_fail_counter, result_ppconfig_fail_counter, result_pp_pllds, ) end # ================================== # Recov """ parse_recov_section!(lines::Base.Iterators.Stateful) Parse Recov section of given stateful iterator of log. # Args - `lines`: Stateful iterator of slave log file lines # Return - `missing`: if failed to parse - `true`: if succeeded - `false` """ function parse_recov_section!(lines::Base.Iterators.Stateful)::Union{Bool, Missing} line = popfirst!(lines) if startswith("====")(line) line = popfirst!(lines) end m = match(r"Test Recov Result = (\d+)", line) if isnothing(m) return missing else return m[1] == "1" end end # ================================== # Main """ parse_slavelog_file(filename::AbstractString) Main function to parse slave log file. Returns `NamedTuple` with keys `adtp`, `power`, `recov` and their values are `Vector` of each results. For a single run result, these `Vector`s have only one element and for a hundred(extra) runs, the lengths are usually 100. For details on results for each section, see documents on dedicated types. """ function parse_slavelog_file(filename::AbstractString) lines_iter = Iterators.Stateful(eachline(filename)) asdtp_results = AsdtpResult[] power_results = PowerResult[] recov_results = Union{Bool, Missing}[] mode::SlaveLogSection = MODE_NONE # main loop while !isempty(lines_iter) # @info "section" mode # each sections if mode == MODE_NONE line = popfirst!(lines_iter) mode = detect_mode_start(mode, line) elseif mode == MODE_QSPIP parse_qspip_section!(lines_iter) mode = MODE_NONE elseif mode == MODE_POWER result = parse_power_section!(lines_iter) push!(power_results, result) mode = MODE_NONE elseif mode == MODE_ASDTP result = parse_asdtp_section!(lines_iter) push!(asdtp_results, result) mode = MODE_NONE elseif mode == MODE_RECOV result = parse_recov_section!(lines_iter) push!(recov_results, result) mode = MODE_NONE end end return (asdtp = asdtp_results, power = power_results, recov = recov_results) end function eff99_count_map(asdtp_results) # try(100) × channel(8) × channel(32) @assert length(asdtp_results) == 100 @assert length(asdtp_results[begin]) == 8 @assert length(asdtp_results[begin][begin]) == 32 map(1:8) do i_asic map(1:32) do i_channel sum(1:100) do i_try asdtp_results[i_try][i_asic][i_channel] != AsdtpMeasurement(0, 1, 0) end end end end end # module SlaveLogParser