ui培训班哪里比较好,百度网站建设优化,自己怎么做微信小程序免费,上海网站开发平台前一篇博客我们提到了#xff0c;如果要使用算法找到Vbr#xff0c;通过寻找APD采集信号的噪声方差的剧变点去寻找Vbr是一个不错的方式。此功能的第一步是在FPGA中实现方差的计算#xff0c;这个我们已经在上一篇博客中实现了。 继上一篇博客之后#xff0c;感觉过了很久了…前一篇博客我们提到了如果要使用算法找到Vbr通过寻找APD采集信号的噪声方差的剧变点去寻找Vbr是一个不错的方式。此功能的第一步是在FPGA中实现方差的计算这个我们已经在上一篇博客中实现了。 继上一篇博客之后感觉过了很久了原因是最近陷入的FPGA在线调试的无线循环。 万事开头难自决定自学FPGA以来已3月有余。 刚开始我以为的万事开头难是如何从零开始在板子上跑个程序。 而真正的万事开头难是根据项目的需求自己写出的第一个具有特定功能的模块。 而就在刚才我经历了千辛万苦终于算是把我第一个模块调通了。要不是我拥有我这个年纪本不该拥有的稳重差点就热泪盈眶啦因为调试过程确实比较曲折。根据以往的经验一旦有所感悟一定要立马记下来好记性不如烂笔头。 但本文绝不是我在这里发表感慨而是我认为确实有一些值得记录的点。本文主要分为三个部分1、第一个自写模块的感悟2、ila在线调试教程3、ila在线调试的技巧和注意事项。 由于目前处于自学初级阶段也只会一些简单的调试技巧后续如果有了新的技巧要不断的添加更新。
1 第一个自写模块的感悟
1.1 明确模块要实现的目标输入输出是什么
先来回顾一下我自己是怎么写出这个模块的一开始肯定是一脸懵逼的不知道从何下手。所以我首先思考的是这个模块的目标是什么更确切一点就是它需要什么输入然后它能够输出什么。 我们的目标是找到某一个通道的APD击穿电压。
需要的输入是 时钟、复位信号、主板温度信息、本模块的使能信号电平使能、该通道下ADC的采样数据。
需要的输出是 找到击穿偏压的标志脉冲信号就是只有一个时钟周期的高电平、ADC采集数据的方差用于调试观测、通道1 APD击穿时对应的DAC码值。由于要修改APD的偏压需要控制DAC而控制DAC的信号在模块外因此需要在此模块中引出。
module find_vbr(input wire clk , // 50M input wire rst_n ,input wire [15:0] temper_front , // PS端传入的主板温度(16位为1表示数据有效数据为8位温度数据90)input wire find_vbr_ena , // 寻找击穿偏压的使能信号input wire [31:0] adc_data_ch1 , // CH1 ADC采样数据output wire vbr_found_flag , // CH1 已找到击穿偏压标志output reg [15:0] adc_var_ch1 , // CH1 ADC采样数据的方差output reg [11:0] vbr_hv_code_ch1 , // CH1 击穿偏压对应的码值output reg [1:0] hv_dac_addr ,output reg [11:0] hv_dac_data ,output reg hv_dac_start
);当然啦模块的输入输出是会在模块的实现过程中增删的这是很正常的事情。所以最开始的时候也不必想的很全面我们对这个模块只需要有一个初步的输入输出定义就好啦。 1.2 拆解目标
有了明确的目标也有了输入输出之后接下来就是思考要实现这个目标我要是实现哪些步骤了。 因此我没先急着写代码而是先写了点注释。
// 0、什么时候开始
// 1、根据温度获取对应Vbr的码值
// 2、Vbr偏压码值 - 0x50
// 3、步长0x08变化偏压码值设置后延迟相应的时间让设置的偏压稳定
// 4、待当前偏压稳定后计算信号的方差
// 5、判断当前信号底噪方差与上一个码值对应的信号底噪方差默认为0的差值是否超过阈值30)
// 6、如果差值超过阈值则Flag拉高如果差值未超过阈值则继续加偏压直到超过阈值为止
// 7、什么时候结束要实现我的目标那就按照上述步骤一步一步实现就可以了。由于以前C语言编写的比较多潜意识里都是串行思路因此在思考和拆解大目标的时候习惯用的是串行思维。 在后续的开发过程中要注意习惯并行思维的应用。当然了即使是到写博客的现在呢我仍然是认为这个模块就应该用串行的思维来思考和拆解。 只是警醒一下自己不要忘记有并行的思维。
1.3 实现目标-硬着头皮写
即使明确了目标也拆解了目标对于一个FPGA初学者来讲要动手去从0到1的实现也是需要很大的魄力的。 开发板的例程你有得抄更注重理解。而现在你真要上了 没有代码给你抄上一篇博客我们其实是参考了C站的C知道给出的答案有点走捷径的感觉你得自己尝试着写了。 这里就只能硬着头皮写了没有捷径没有任何技巧。当然了硬着头皮写的前提是基于开发板的基础学习还是要扎实的不然你头发掉光了也是写不出来的多少有点自欺欺人了。
硬着头皮写呢有时候也会陷入一种瞻前顾后犹豫不决害怕失败的感觉迟迟不敢往下写这是正常的。 我可以肯定的告诉你你第一把写出来的程序百分之百有问题。 你根本不用担心失败不失败的问题因为肯定有问题。 你先写出来我们主要追求的是一个完整性。 别看功能也不复杂硬着头皮完整写完这个步骤我基本上花了1周的时间。 调试我花了两个周哈哈。 在后面的调试过程中我又做了很多修改。 我觉得其中值得注意的一点就是 你要在草稿纸上简单画一画时序图 你希望你的这些信号的时序图长什么样子。 这是你在实现的时候思考和关注的问题。后续有在调试的时候也要看实测抓出的波形是不是如你设计的那样。
我把最终成功运行的代码贴出来吧供参考和备忘。
timescale 1ns / 1psmodule find_vbr(input wire clk , // 50M input wire rst_n ,input wire [15:0] temper_front , // PS端传入的主板温度(16位为1表示数据有效数据为8位温度数据90)input wire find_vbr_ena , // 寻找击穿偏压的使能信号input wire [31:0] adc_data_ch1 , // CH1 ADC采样数据output wire vbr_found_flag , // CH1 已找到击穿偏压标志output reg [15:0] adc_var_ch1 , // CH1 ADC采样数据的方差output reg [11:0] vbr_hv_code_ch1 , // CH1 击穿偏压对应的码值output reg [1:0] hv_dac_addr ,output reg [11:0] hv_dac_data ,output reg hv_dac_start
);//
// Parameter define
//
parameter THRESHOLD 50;
parameter MAX_WAIT_COUNT 100_000_000 - 1; // 20ns x 100_000_000 2 s
parameter HVCODE_STEP 8; // 偏压码值变化8偏压实际变化约等于0.25V
parameter DEFAULT_HVCODE 12h4E0; // 默认APD偏压码值(修改后可设置默认偏压)
parameter APD_SET_DELAY 32d500_000; // 设置单通道APD后等待时间
parameter APD_SET_WIDE 32d500; // 设置使能脉宽// parameter THRESHOLD 30;
// parameter MAX_WAIT_COUNT 20 - 1; // 20ns x 100_000_000 2 s
// parameter HVCODE_STEP 8; // 偏压码值变化8偏压实际变化约等于0.25V
// parameter DEFAULT_HVCODE 12h4E0; // 默认APD偏压码值(修改后可设置默认偏压)
// parameter APD_SET_DELAY 32d20; // 设置单通道APD后等待时间
// parameter APD_SET_WIDE 32d10; // 设置使能脉宽//
// Internal Signals
//
(* MARK_DEBUGtrue *) wire [11:0] rom_hv_code; // rom查找的APD偏压码值 官方给出的击穿电压再减去2V所对应的码值
(* MARK_DEBUGtrue *) reg [27:0] wait_cnt; // 延迟计数变量
(* MARK_DEBUGtrue *) wire [11:0] pre_hv_code;
(* MARK_DEBUGtrue *) reg [11:0] cur_hv_code;
(* MARK_DEBUGtrue *) reg is_init;
(* MARK_DEBUGtrue *) reg is_finish;
(* MARK_DEBUGtrue *) reg [31:0] apd_set_wait_cnt; //自动设置状态停留计数(* MARK_DEBUGtrue *) reg is_hv_can_be_set; // 偏压是否进入可设置状态
(* MARK_DEBUGtrue *) reg is_hv_can_be_wait; // 偏压是否进入等待响应状态
(* MARK_DEBUGtrue *) reg is_hv_set_completed; // 偏压设置是否已完成
(* MARK_DEBUGtrue *) wire [15:0] cur_var; // 当前信号方差
(* MARK_DEBUGtrue *) wire var_available; // 当前信号方差可用
(* MARK_DEBUGtrue *) reg [15:0] his_var; // 历史信号方差
(* MARK_DEBUGtrue *) reg [15:0] delta_var; // 方差变化量// (* MARK_DEBUGtrue *) reg rst_n;
// reg [7:0] rst_counter; // 默认为0
// parameter RESET_COUNT_MAX 100;// always (posedge clk) begin
// if (rst_counter RESET_COUNT_MAX) begin
// rst_counter rst_counter 1;
// end
// else if(rst_counter RESET_COUNT_MAX)begin
// rst_counter rst_counter;
// end
// else begin
// rst_counter d0;
// end
// end// always (posedge clk) begin
// if(rst_counterRESET_COUNT_MAX) begin
// rst_n 1b1;
// end else begin
// rst_n 1b0;
// end
// end//----------------------------- pre_hv_code -----------------------------
// assign pre_hv_code (temper_front[15] 1b1) ? rom_hv_code:DEFAULT_HVCODE; // 验证温度数据是否有效(当disable拉高时上电设置默认偏压值)
assign pre_hv_code DEFAULT_HVCODE; APD_rom R_APD_rom ( // 通过Rom读取当前温度对应的官方击穿偏压 - 2V所对应的码值.a(temper_front[7:0]), // input wire [7:0] a.spo(rom_hv_code) // output wire [11:0] spo
);//----------------------------- is_init -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginis_init 1b0; endelse if(find_vbr_ena 1b1 is_init 1b0) begin // 当前条件下初始化要设置的偏压码值current_HVCODEis_init 1b1;endelse beginis_init is_init;end
end//----------------------------- is_init -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginis_finish 1b0; endelse if(var_available1b1 delta_var THRESHOLD) begin // 当前条件下初始化要设置的偏压码值current_HVCODEis_finish 1b1;endelse beginis_finish is_finish;end
end//----------------------------- is_finish ----------------------------- // 若方差已计算且方差变化量大于等于阈值则结束。
// assign is_finish (var_available1b1 delta_var THRESHOLD) ? 1b1:1b0;//----------------------------- cur_hv_code -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) begincur_hv_code DEFAULT_HVCODE; endelse if(find_vbr_ena 1b1 is_init 1b0) begincur_hv_code pre_hv_code - 12h040; endelse if( (is_hv_set_completed 1b1) (var_available 1b1) (is_finish 1b0) cur_hv_code 12h578) begin // 若已初始化方差已计算且未结束则偏压码值按固定步长增长进入下一轮。 cur_hv_code cur_hv_code HVCODE_STEP;endelse begincur_hv_code cur_hv_code;end
end//----------------------------- is_hv_can_be_set -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginis_hv_can_be_set 1b0; endelse if(find_vbr_ena 1b1 is_init 1b0) begin // 初始化后isHvCanBeSet被拉高is_hv_can_be_set 1b1;endelse if( (is_hv_set_completed 1b1) (var_available 1b1) (is_finish 1b0)) begin // 若已初始化方差已计算且未结束isHvCanBeSet被拉高 is_hv_can_be_set 1b1;end else if ( (is_hv_can_be_set 1b1) (is_hv_can_be_wait1b1)) beginis_hv_can_be_set 1b0;end else beginis_hv_can_be_set is_hv_can_be_set;end
end//----------------------------- hv_dac_starthv_dac_data ----------------------------- // 设置偏压操作
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginhv_dac_addr 2d0;hv_dac_start 1b0;apd_set_wait_cnt d0; endelse if(is_hv_can_be_set 1b1)beginif (apd_set_wait_cnt APD_SET_DELAY / 2) beginhv_dac_start 1;endif (apd_set_wait_cnt (APD_SET_DELAY / 2) (APD_SET_WIDE / 2)) begin hv_dac_data cur_hv_code;endif (apd_set_wait_cnt APD_SET_DELAY / 2 APD_SET_WIDE) beginhv_dac_start d0;endif (apd_set_wait_cnt APD_SET_DELAY) beginapd_set_wait_cnt d0;endelse beginapd_set_wait_cnt apd_set_wait_cnt 1b1;end end
end//----------------------------- is_hv_can_be_wait -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginis_hv_can_be_wait 1b0; endelse if (apd_set_wait_cnt APD_SET_DELAY) beginis_hv_can_be_wait 1b1;endelse if (is_hv_can_be_wait 1b1 wait_cnt MAX_WAIT_COUNT) beginis_hv_can_be_wait 1b0;endelse beginis_hv_can_be_wait is_hv_can_be_wait;end
end//----------------------------- wait_cnt ----------------------------- // 计数两秒
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginwait_cnt d0; endelse if(is_hv_can_be_wait 1b1) beginif (wait_cnt MAX_WAIT_COUNT) beginwait_cnt d0;endelse beginwait_cnt wait_cnt 1b1;endendelse beginwait_cnt d0;end
end//----------------------------- is_hv_set_completed -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginis_hv_set_completed 1b0; endelse if (is_hv_can_be_wait 1b1 wait_cnt MAX_WAIT_COUNT) begin // 偏压设置完成后计数两秒等待结束isHvSetCompleted被拉高is_hv_set_completed 1b1;endelse if (is_hv_set_completed 1b1 var_available 1b1) begin // 方差计算完成后isHvSetCompleted被拉低is_hv_set_completed 1b0;endelse beginis_hv_set_completed is_hv_set_completed;end
end//----------------------------- var_compute -----------------------------
var_compute var_calculator (.clk ( clk ) ,.rst_n ( rst_n ) ,.data_in ( adc_data_ch1[7:0] ) ,.valid_in ( is_hv_set_completed ) , .variance ( cur_var ) ,.valid_out ( var_available )
);//----------------------------- delta_var ----------------------------- // 方差变化量
//assign delta_var (var_available 1b1 cur_var his_var) ? (cur_var - his_var) : d0;//----------------------------- vbr_found_flag ----------------------------- // 是否找到击穿偏压
assign vbr_found_flag (delta_var THRESHOLD) ? 1b1 : 1b0;//----------------------------- his_var -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) beginhis_var d0; endelse if(var_available 1b1) begin // 如果当前偏压并不是击穿偏压则记录历史方差以便于后续计算方差变化量his_var cur_var;endelse beginhis_var his_var;end
end//----------------------------- delta_var -----------------------------
always (posedge clk or negedge rst_n) beginif (rst_n 1b0) begindelta_var d0; endelse if(var_available 1b1) begin // 如果当前偏压并不是击穿偏压则记录历史方差以便于后续计算方差变化量if (cur_var his_var) begindelta_var cur_var - his_var;endelse begindelta_var d0;endendelse begindelta_var delta_var;end
endendmodule1.4 前仿真和后仿真
硬着头皮写完之后呢就是进行仿真了有经验的老师傅说一般来讲前仿真功能仿真通过了在线就不会有太大的问题。 而后仿真呢是最接近与上机跑的真实情况的但是后仿真编译的时间也比较长所以很少有人搞后仿真的。 Modelsim搞仿真还是有一套的再次安利一波。 在讲仿真的这里我特别想强调的一个点就是一定要尽量模拟真实的情况。 否则你所认为的仿真“通过”了就很片面局限。 仿真这里一定要考虑全面。 我就是吃了这方面的亏 在上一篇博客我实现了一个方差计算小模块我用的输入数据是0~255这个数据仿真是没问题的V2.0。 但是我在用真机调试的时候就出现了问题。 一方面在计算方差的时候位宽的问题我也没有考虑周到一方面方差的计算精度我也没有考虑到最后优化后都是V4.0了。由于仿真的粗心在调更大模块的时候我的先验知识就让我不要去考虑是不是方差计算模块出了问题而是去考虑其他地方的问题但是恰巧就是方差模块出了问题这样就导致了无法准确定位的问题真正的位置。 所以在仿真的时候 要考虑全面细致。 比如我们的真实数据是在7f和80之间来回变化那么我们如何在Testbench代码中把真实的数据模仿出来这个是要好好考虑的问题就在写博客的时候我已经想到如何实现了比如根据求计数器余数的办法给出是7f是80所以别畏难肯定有办法。 在调试的时候由于一直无法定位问题后仿真我也测试过也是“通过”的。但是上机还是通不过我还怀疑是板子硬件有问题还去换了板子测试结果是一样的。 所以仿真的全面和细致真的很重要另外对于所谓的仿真“通过”是要保持一颗怀疑的心的。 1.5 对自己吹一口彩虹屁
之前在调试的时候总是找不到问题也请教了前辈但是仍然没有解决问题。 他们就说我的这种实现方式没有用状态机有点不稳定很容易出问题。 我当时也认同没有用状态机可能程序没那么稳定的观点。 我也想过要不就用状态机重新实现一遍。但如果让我稀里糊涂的重构用状态机实现我心里是不甘心不服气的。即使我的实现方式有问题那我也一定要找到我目前这种实现方法的问题在那里不然不明不白的重写我是无法接受的。 我的确也没有重构通过两星期持续的坚持调试我最终定位到了问题并且也解决了。 戏剧的点是其实根本就不是我实现方式的问题而是方差子模块的问题。 因此我要感谢自己感谢自己的不甘心感谢自己的不服气。学习FPGA编程的态度当如是也
2 ila在线调试教程
ila是一种FPGA常用的在线调试方式和DSP、STM32的断点调试不同ila是通过抓取信号来判断你的程序是否正常运行的。学习ila我是看了B站的一个up主的视频的Vivado在线调试工具ILA使用教程【小梅哥FPGA】_哔哩哔哩_bilibili全程1个半小时很受用。如视频所说的用ila实现在线调试的方式有好几种在这里呢我把我最近用的这种方式记录下来供大家和未来的自己参考。
第一步
在所有在线调试需要抓取的变量前 添加(* MARK_DEBUGtrue *)
(* MARK_DEBUGtrue *) wire [11:0] rom_hv_code; // rom查找的APD偏压码值 官方给出的击穿电压再减去2V所对应的码值
(* MARK_DEBUGtrue *) reg [27:0] wait_cnt; // 延迟计数变量
(* MARK_DEBUGtrue *) wire [11:0] pre_hv_code;
(* MARK_DEBUGtrue *) reg [11:0] cur_hv_code;
(* MARK_DEBUGtrue *) reg is_init;
(* MARK_DEBUGtrue *) reg is_finish;
(* MARK_DEBUGtrue *) reg [31:0] apd_set_wait_cnt; //自动设置状态停留计数第二步
综合电路 第三步
打开综合设计 正常的话会等待一段时间 第四步 第五步
Next三下 第六步
添加带观测变量并设置时钟域 第七步
选择采样的数据深度、勾选捕获和触发。 第八步
Finish 第九步
后面会出现一堆提示一路OK下去。 第十步
重新综合、布线、生成bit文件以便后续烧写程序在线调试 3 ila在线调试的技巧和注意事项 在工程比较大的时候在vivado版本比较低的时候在你不熟悉vivado的时候你去用ila在线调试你会遇到各种奇葩的问题解决办法也很奇葩。 只要你动了项目里微小的东西比如加个IP核减个IP核甚至哪怕你源代码中多了一个空格。同样的工程以前编译能够通过的现在很有可能编译通不过了。 编译通不过的原因一般出现时布局布线上面这里面有很多随机性。 同样地你在ila调试的时候你增加一个观测变量你删除一个观测变量。 都有可能造成编译无法通过最终无法生成bit文件。
调试技巧1
当你编译通不过了你尝试删除几个ila的观测变量如果再通不过那就再删除几个。 你这次删除后编译通过了下次编译你再慢慢加上去也可以的。
调试技巧2
如果编译通过了硬件上电正常仿真器连接都正常但是你始终没办法打开你的硬件。 解决办法可以是重新打开另一个vivado 调试技巧3
正常的调试流程也稍微说一下
先把程序烧写到板子上 选择.bit文件 下载之后Refresh device一下这个操作一定别忽略。 在触发设置的窗口添加触发信号 可以用 这个加号添加也可以用拖动的方式。 然后设置触发条件并且运行。 运行之后如果系统捕获到了你的触发条件那么波形窗口就会显示出来。 这个按钮是连续触发的意思你先选中这个按钮然后再点运行它就会根据你的触发条件连续不断的触发。刷新你的波形数据。 在调试一些需要观察数据变化的时候可以用使用。 另外当你想要用多个信号来进行触发的时候需要点击这个按钮。 调试技巧4
调试的过程一般是由顶层逐步向下再展开去看信号是否正常 比如顶层的toptop里面实例化了一个 find_vbr子模块叫inst_find_vbr, find_vbr子模块里面实例化了一个var_compute子模块 叫inst_var_compute。 你调试的过程应该是先看 top层的信号正不正常 再看find_vbr层的信号正不正常再看var_compute层的信号正不正常。 逐步的深入。 这三层模块的内部信号都是可以使用(* MARK_DEBUGtrue *)标记然后在线调试观测的。
调试技巧5
如果你想判断程序是否执行了某个条件那么你可以添加一个test_flag变量 复位的时候拉低然后在你想检测条件下面把这个flag拉高。 这样我们就可以判断出这个条件是否被执行过。 注意事项
代码只要烧写进去了它就会自己跑起来不会等你点 这个按钮它才开始跑。
比如你自己写了一个内部的复位信号 你在调试的时候你是抓不到rst_n的上升沿的。 复位时间是小于1ms的因为等你去抓的时候人家早就已经拉高了。
(* MARK_DEBUGtrue *) reg rst_n;
reg [7:0] rst_counter; // 默认为0
parameter RESET_COUNT_MAX 100;always (posedge clk) beginif (rst_counter RESET_COUNT_MAX) beginrst_counter rst_counter 1;end else if(rst_counter RESET_COUNT_MAX)beginrst_counter rst_counter;endelse beginrst_counter d0;end
endalways (posedge clk) begin if(rst_counterRESET_COUNT_MAX) begin rst_n 1b1;end else begin rst_n 1b0;end
end越学习越觉得自己无知后面应该会有一篇讲VIO的博客。欢迎大家留私信或者评论区讨论。 分享大家的调试问题和技巧。
未完待续...