OFDM IP-ядра
作者
module OFDMIPDemo
using Statistics, LinearAlgebra, JSON
using Main: default, scatter, scatter!, plot, plot!
const EN = Main.engee
const FS = 30.72e6
const NFFT = 2048
const CP = 64
const ND = 1792
"Настройка существующей модели без перестройки IP-ядер."
function configure!(dir; modulation=0, duration=0.001)
modulation in (0,1) || error("В примере проверены BPSK (0) и QPSK (1)")
name="ofdm_model"
EN.open(name)
params=Dict("Constant"=>"UInt16(2112)","Constant-1"=>"UInt16(1792)",
"Constant-2"=>"UInt8(0)","Constant-3"=>"UInt8($modulation)",
"Constant-4"=>"UInt8(1)","Constant-5"=>"UInt8(5)",
"Constant-6"=>"UInt16(128)","Constant-7"=>"UInt16(127)",
"Constant-8"=>"UInt16(64)","Constant-9"=>"UInt8(1)","Constant-10"=>"UInt8(32)")
for (block,value) in params
EN.set_param!(name*"/"*block,"Value"=>value)
end
EN.set_param!(name*"/OFDM data generator",
"SourceFiles"=>joinpath(dir,"data_gen_src","ofdm_data_generator.c"),
"IncludeDirectories"=>joinpath(dir,"data_gen_src"))
EN.set_param!(name,"FixedStep"=>"1/30.72e6","StopTime"=>string(duration))
nothing
end
"Получение только действительных отсчётов и проверка полных полезных OFDM-символов."
function measure(results; modulation=0)
sources=Dict(:data_in_ws=>("Real-Imag to Complex-2",nothing),
:data_out_ws=>("Gain-5",nothing), :valid_in_ws=>("OFDM data generator",nothing),
:valid_out_ws=>("OFDMex-IP-3","valid_out"),
:data_mod_ws=>("OFDMex-IP-2","data_out"), :valid_m_ws=>("OFDMex-IP-2","valid_out"))
function frame(name)
block,suffix=sources[name]
matching=[k for k in keys(results) if startswith(last(split(k,'/')),block*".") &&
(isnothing(suffix) || endswith(k,"."*suffix))]
length(matching)==1 || error("Missing or ambiguous signal $name: $(collect(keys(results)))")
collect(results[only(matching)])
end
input=frame(:data_in_ws); output=frame(:data_out_ws)
valid_in=frame(:valid_in_ws); valid_out=frame(:valid_out_ws)
modulated=frame(:data_mod_ws); valid_mod=frame(:valid_m_ws)
@assert input.time==valid_in.time "Время входа и valid не совпадает"
@assert output.time==valid_out.time "Время выхода и valid не совпадает"
x=input.value[valid_in.value .> 0]
y=output.value[valid_out.value .> 0]
n=div(min(length(x),length(y)),ND)*ND
n>=5*ND || error("Для проверки нужны не менее пяти полных полезных символов")
# Порты valid уже компенсируют задержку конвейера. Порядок данных сохраняется.
# Здесь нет подгонки задержки, фазы или амплитуды по принятому сигналу.
reference=ComplexF64.(x[1:n]); received=ComplexF64.(y[1:n])
evm=100*norm(received-reference)/norm(reference)
re_active=abs.(real.(reference)).>0.01
im_active=abs.(imag.(reference)).>0.01
bit_errors=count(real.(reference[re_active]).*real.(received[re_active]).<0)+
count(imag.(reference[im_active]).*imag.(received[im_active]).<0)
bits=count(re_active)+count(im_active)
metrics=Dict{String,Any}("modulation"=>modulation==0 ? "BPSK" : "QPSK",
"Nfft"=>NFFT,"Nd"=>ND,"cp"=>CP,"window"=>32,"sample_rate"=>FS,
"input_valid"=>length(x),"output_valid"=>length(y),
"compared_symbols"=>div(n,ND),"compared_samples"=>n,
"evm_percent"=>evm,"bit_errors"=>bit_errors,"bits_compared"=>bits,
"alignment_offset"=>0,"input_headroom"=>0.5,"output_gain"=>2.0)
@assert evm < 1.0 "EVM превышает 1 %"
@assert bit_errors == 0 "Есть ошибки решений по битам"
(; metrics,reference,received,input,output,valid_in,valid_out,modulated,valid_mod)
end
"Запуск модели, извлечение результатов, проверка критериев и закрытие модели."
function run_case(dir;modulation=0,duration=0.001)
name="ofdm_model"
if name in [m.name for m in EN.get_all_models()]
EN.close(name;force=true)
end
model=EN.load(joinpath(dir,name*".engee");force=true)
try
configure!(dir;modulation,duration)
for port in ["Real-Imag to Complex-2/1","Gain-5/1","OFDM data generator/3",
"OFDMex-IP-2/1","OFDMex-IP-2/2","OFDMex-IP-3/2"]
EN.set_log(name,port)
end
results=EN.run(model;verbose=false)
errors=filter(x->get(x,:type,"")=="ERROR",EN.get_logs(model))
isempty(errors) || error(string(errors))
measure(results;modulation)
finally
EN.close(name;force=true)
end
end
"Графики созвездия, ошибки и сигналов valid для сохранения в примере."
function result_plot(result)
default(fontfamily="sans-serif",background_color=:white,foreground_color="#293640",
gridcolor="#d7e0e6",guidefontsize=9,titlefontsize=10,tickfontsize=8,legendfontsize=8)
ref=result.reference; rx=result.received; m=result.metrics
keep=abs.(ref).>0.01
p1=scatter(real.(rx[keep]),imag.(rx[keep]),markersize=2,markerstrokewidth=0,
color="#1b84b8",label="Приём",xlabel="I",ylabel="Q",aspect_ratio=:equal,
title="$(m["modulation"]) — EVM $(round(m["evm_percent"];digits=3)) %")
scatter!(p1,real.(ref[keep]),imag.(ref[keep]),markersize=6,markershape=:cross,
color="#b94754",label="Эталон")
k=1:min(180,length(ref))
p2=plot(k,real.(ref[k]),color="#b94754",label="Эталон I",xlabel="",ylabel="Амплитуда")
plot!(p2,k,real.(rx[k]),color="#1b84b8",linestyle=:dash,label="Приём I")
p3=plot(abs.(rx-ref),color="#7254b3",legend=false,xlabel="Действительные отсчёты",ylabel="Модуль ошибки",title="Модуль ошибки",xticks=0:3000:length(ref),xformatter=:plain)
ni=min(18000,length(result.valid_in.value))
t=result.valid_in.time[1:ni].*1e6
p4=plot(t,Float64.(result.valid_in.value[1:ni]),color="#1b84b8",label="valid входа",xlabel="Время, мкс",ylabel="valid",title="")
plot!(p4,t,Float64.(result.valid_mod.value[1:ni]).+1.4,color="#7254b3",label="valid модулятора + 1.4")
plot!(p4,t,Float64.(result.valid_out.value[1:ni]).+2.8,color="#17834e",label="valid приёма + 2.8")
plot(p1,p2,p3,p4;layout=(2,2),size=(1200,900),legend=false)
end
end