Introduction
功耗是当今许多技术都要考虑的重要因素。例如,手机生产商总是谈论他们在电源管理方面的改进,以及如何延长电池的使用寿命。功能与功耗之间的平衡是许多人都在研究的有趣课题。然而,当我们做实验时,我们很少会考虑我们消耗了多少电能,因为我们从来没有受到电能的限制。在本课实验 3 中合成巨型鼓时,我们耗尽了 FPGA 上的所有 DSP 块,并使用了大量 M10K 内存。这种对硬件的大量使用很可能会导致高功耗,我们很想知道有哪些因素会影响 FPGA 的功耗。带着这个目标,我们构建了一个电路,使用基于 I2C 的电流传感器 MIKROE-2987 测量流经电源电缆的电流,同时假设有稳定的 12V 电压供应,并在 FPGA 中实施了一个带积分器的 I2C 接收器,以持续增加消耗的能量。根据获得的数据,我们在 VGA 上建立了一个直观的用户界面,显示电流、实时功率和平均能量,以监控 DE1-SoC 的功耗。
Design & testing methods
design overview
功率估算器的整体设计如图 1 所示。MIKOE-2987 利用霍尔效应测量通过内部熔断器输入引脚的电流,具有约 1.2m Ω 的极低串联电阻,不会影响提供给 FPGA 的电流。
该电路板使用 I2C 接口(SDA 表示数据,SCL 表示时钟)来传输经过处理的电流数据。根据规范,我们在 FPGA 中构建了 I2C 主站作为接收器,将比特流转换为实际的高分辨率电流数据,并以有效信号传递给 HPS。
该接收器还包含一个乘法器和积分器,分别用于计算实时功率和累积能量。它由 HPS 控制,以实现复位、启动和停止。HPS 将处理数据,并在 VGA 上显示相应的用户界面。为了加快调试,我们还采用了 7 段显示器来显示测量到的电流。
图 1:功率估算器的总体设计
Hardware design
我们都知道物理课上的公式 P=I∗V 表示功率,W=∑tP∗dt 表示能量,这两个简单的公式就是本项目的精髓。根据电源适配器的规格,可提供稳定的 12V 电压。在这种情况下,实时功率通过一个乘法器来计算 I∗12。
至于消耗的功率,我们设计了一个与 Lab1 相同的积分器,在计算开始后,以单位时间 P∗dt 为每个可用采样点增加能量(dt 取决于采样率)。
这个项目的关键在于我们需要一种方法来测量电源适配器提供的电流,以便计算功率和能量。在网上进行了一番研究后,我们决定使用 MIKROE-2987 电流测量板,原因如上所述。
该电流传感器的输出进入一个 12 位 ADC,并通过 MCP3221 的 I2C 接口提供。因此,我们在 FPGA 中建立了 I2C 主接收器,以便与之通信。
MCP3221 的 I2C 协议如图 2 所示。I2C 通信只涉及 2 根导线:SDA 用于数据,SCL 用于时钟。SCL 是单向的,由 I2C 接收器提供,而 SDA 是双向的,可由主设备(I2C 接收器)或从设备(MCP3221)控制。
图 2:MCP3221 指定的 I2C 协议
根据数据手册,主站和从站之间的每个事务长度应为 8 位,并总是在 ACK/NAK 之后。当 MCP3221 不忙时,SDA 和 SCL 都将置高。
要启动数据交易,主设备必须在 SCL 为高电平时将 SDA 线路从高电平拉低到低电平,从而发出启动条件信号。
当芯片被唤醒时,它将接收来自主站的地址字节,以便在 SDA 中配置芯片。前 4 位是设备位,后 3 位是地址位,最后一位是 R/W 选择位。我们将其默认配置为 1001_101_1,以便从芯片读取数据。
当接受到一个有效的地址字节时,芯片会将 SDA 设置为低电平作为 ACK 信号,然后开始提供数据。每次提供的数据以字节为单位。当提供完一个字节后,需要主站发出低电平有效的 ACK 信号,以便从站继续提供数据。
由于数据为 12 位形式,因此需要两个字节(我们称之为上字节和下字节)来完成一个数据交易,上字节的前四位为零。
当主站发出连续 ACK 时,从站将继续提供当前值,而不会重复地址字节。
要终止交易,主站可在 SCL 也为高电平时将 SDA 拉高,从而产生 NAK;在 SCL 为高电平时将 SDA 从低电平拉高,从而以停止条件结束交易。值得注意的是,SDA 线路的变化应始终发生在 SCL 为低电平时,并在时钟的高电平部分保持稳定(启动和停止条件除外),而且每个数据位应有一个时钟脉冲。
根据上述 I2C 协议,我们在 SystemVerilog 中开发了一个 11 状态的 FSM,如图 3 所示。
在 IDLE 状态下,SDA 和 SCL 都将切换。当检测到来自 HPS 的启动信号时,将提供启动条件,然后传输地址字节。
当内部计数器检测到地址字节已传送完毕时,将对从站进行确认。否则,主站将回到 IDLE 状态。如果提供了 ACK,主站将在收到数据后接收从站的上下字节。
在 STATE_DATA_RECEIVED_UPPER 和 STATE_DATA_RECEIVED_LOWER 状态下,主站从从站接收数据,并将其放入 16 位长的 FIFO 中。
当 HPS 通过拨动停止信号指示停止系统时,将提供 NACK,然后提供停止条件,随后进入等待状态,以确保 MCP3221 不再忙。
值得注意的是,由于 SDA 线路可同时由主设备和从设备控制,我们编写了一个三态缓冲器来决定由哪个设备控制该线路。
三态缓冲器带有一个使能信号,当主设备在事务中充当输入时,该信号为高电平,当该使能信号断言时,SDA 在主设备端应变为高阻抗。
否则,SDA 从主站输出。在不发送启动/停止条件、不等待或不空闲时,SCL 时钟线作为 200K Hz 时钟信号跟随输入。当主站确认数据的较低位或主站发出 NAK 时,数据有效信号被拉为高电平,表明我们可以从 MCP3221 获取正确的值。该信号稍后将用于控制计算累积能量的积分器。
通过 I2C 接收器,我们可以获得 12 位数据,但仍需将其转换为实际电流数据,我们将其设置为 9.23 位定点数据。相应的电压可计算为 d a t a o u t 2 12 ∗ 3.3 \frac{dataout}{2^{12}} * 3.3 212dataout∗3.3 V。根据规范,电流传感器的灵敏度为 110mv/A,原点为 1.65V,因此我们可以计算出实际电流为 I = V − 1.65 V 110 m V / A I= \frac{V-1.65V}{110mV/A} I=110mV/AV−1.65V。
MCP3221 I2C 接口还规定它能达到的最大时钟频率为 400kHz,我们选择 200KHz 应该是一个合理的选择。
这意味着 50MHz 的 FPGA 时钟速度过快,无法运行,因此我们增加了一个时钟分频器,将其降到 200kHz。
如上所述,我们接着实施了一个积分器,通过在单位时间内不断增加能量来计算累积能量。
我们使用 HPS 发出的停止信号来暂停积分,然后继续启动。只要停止信号未被断言,且 I2C 接口的数据有效,我们就会通过计算 V∗I∗dt 来计算新的单位能量,并将其与消耗的总能量相加。
dt 的值是通过使用 SignalTap 计数两个连续数据有效信号之间的脉冲数确定的。我们计算了 18 个脉冲,与协议相符(16 个脉冲用于传输数据,2 个脉冲用于 ACK)。
这意味着我们在每次新的电流采样时的单位能量为 V ∗ I ∗ 18 200000 \frac{V∗I∗18}{200000} 200000V∗I∗18。尽管输入电流使用的是 7.23 定点值,但我们的积分器模块的能量输出被设置为 41.23 定点,即 64 位长数据,因为能量是累积的,所以需要更多位数来防止溢出。
但是,Qsys PIO 的最大位宽只有 32 位,因此我们必须创建两个 PIO 端口,一个用于传输较高的 32 位,另一个用于传输较低的 32 位。然后,我们使用 long long 类型变量在 HPS 中存储 64 位值,之后通过除法 223 将其从定点转换为双倍。位数增加一倍也提高了读数精度。
每当电流有效时,我们也会递增一个计数器,通过计算 (cycle_count - 1)*KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input: …rac{18}{200000] 就能确定 FPGA 开始运行后所经过的时间。这里我们不考虑第一个事务的时间,即不计算前 27 个周期,因为其中包括传输地址比特的时间,而这并不代表中间的事务。
为了使调试更加直观,我们在 FPGA 上实现了一个简单的段解码器来显示测量的电流值。如上所述,电流值为 9.23 固定点,我们使用分段显示器 0 作为符号,后面两位作为整数,其余作为分数部分。我们建立了一个映射表,将前 9 位作为整数,小数点后 23 位中的 4 位作为分数。
Software design
所有需要的硬件都已实例化,我们开始添加与 HPS 的通信。由于我们的目标是在 VGA 显示屏上以图形方式显示数据,因此从软件方面着手更为方便。
为了保证软件的正确性,我们在设计软件时采用了渐进式方法。首先,我们通过在 Qsys 总线上添加 PIO,允许数据从 FPGA 流向 HPS。
每个 PIO 都有一个来自轻量级 AXI 主站的偏移量,我们可以通过查看地址:偏移量 + 轻量级 AXI 主站地址来访问数据。
由于积分器模块的输出是能量而不是功率,我们需要进行一些转换。每个给定实例的动态功率等于瞬时电流 * 12V,平均功率等于累积能量/总耗时。在实际绘制这些数据之前,我们要确保我们的数据是有意义的,因此每当数据有效信号被拉到高电平时,我们就会在串行监视器上打印电流值、动态功率、平均功率、周期计数和时间。
在这个过程中,我们意识到需要有意让程序休眠一段时间,因为 HPS 的运行速度比 FPGA 快得多,所以数据有效信号的一个高电平部分将对应 HPS 的数百个周期,从而产生比需要多得多的数据点。
在确认数据正确无误后,我们需要一种适当的方式来显示数据。我们决定在 VGA 上显示三个图表:电流与时间、动态功率与时间、平均功率与时间。
我们将屏幕上的时间刻度限制为 3 秒,这意味着每隔 3 秒就会擦除图表并使用新数据重新绘制。Y 轴刻度由变量决定。就电流而言,它受限于我们电流传感器的能力,该传感器最多可处理 2 安培的电流。对于动态功率和平均功率,最大值为 IMax∗VMax=2∗12=24W。
因此,从 y 轴的底部开始,每增加一个像素,功率就增加 24 119 \frac{24}{119} 11924 瓦。我们还在屏幕右侧创建了一个文本框,用于显示这些变量的实际值。最终的 VGA 显示界面如图 4 所示。
在这里插入图片描述
图 4:在 VGA 上显示图表
在 VGA 上绘制数据后,我们开始将更多的控制权转移到 HPS 端。我们首先禁用了启动、停止和校准信号的按钮控制,采用了纯软件控制。
这就需要一个能检测键盘输入的用户界面,我们通过使用多个 pthreads 实现了这一点。用户可以在 1-3 之间选择一个数字,每个数字分别对应按下重置、停止或校准键。当检测到停止时,绘图会立即停止,数据显示也会冻结,而启动则会恢复绘图。
另一方面,校准会清除能量和时间数据,并从 0 重新开始。选择校准后,所有图形都将清除并立即开始重新绘制。下面的视频演示了校准的效果。
Testing strategy
为了确保设计中的所有模块都能按预期工作,我们采用了分层测试方法。在最底层,我们首先编写了一个测试平台,根据上述 I2C 协议输入控制信号、时钟和数据,测试 I2C 状态机,并在 ModelSim 中验证输出波形。
如图 5 所示,主站能够发送正确的地址位,在适当的时候生成 ACK,读取输入数据,并在 12 位事务完成时将数据有效信号拉高。
图 5:用于 I2C 主站实施的测试平台的 Modelsim 波形
在仿真中证明 I2C 接口功能正常后,我们在 Quartus 中实例化了模块,并使用 SignalTap 验证了来自 FPGA 的信号反馈。SignalTap 在 FPGA 中创建了真正的硬件电路,并提供了电路产生的实际输出波形。
在使用 SignalTap 时,我们使用实验室的电流源直接向电流传感器馈送电流,因此我们清楚地知道电流输出的预期值。我们还使用 FPGA 上的按钮来控制复位、启动和停止信号,因此可以轻松触发 SignalTap 的数据捕获。通过比较电源上的电流输出和 SignalTap 的数据输出,我们可以知道我们的实现是否正确。
在项目的最后,我们不再使用电流源,而是直接测量 FPGA 的电流。我们在面包板上放置了一个 2.1 毫米内螺纹直流电源插孔,并将 FPGA 电源适配器连接到该插孔。
使用跳线将电源插孔的电源引脚连接到 mikroe-2987 电路板的 P+ 引脚,电流将通过电路板到达其 P- 引脚,我们将另一根跳线连接到公头插孔至开口电源线的正端。
公电源插孔插入 FPGA,而其负端则连接回面包板上母电源插孔的接地引脚。
这种设置形成了一个串联电路,其中电流传感器测量的电流应与 FPGA 的电流相同。图 6 和图 7 显示了我们如何设置这个串联电路。我们观察屏幕上的图形和不同读数,以确定数据是否合理。
在这种设置下,我们观察到电流和功率读数相当稳定,这在意料之中,因为 FPGA 始终在运行相同的程序。在这一阶段,我们还改用纯软件控制而非按钮,并将 VGA 输出在软件中发出信号时的行为与预期进行了比较。
我们可以看到在选择停止时图形停止,而在开始时又恢复绘制。校准工作也正常进行,因为图形被擦除,时间轴被正确重置,电流和能量的数据显示回到 0。
图 6:直接测量 FPGA 电流的电路设置
图 7:近距离观察电流传感器周围的电路
Results & Analysis
在对系统进行上述进一步验证后,我们能够利用它来探索 FPGA 中一些有趣的功耗行为。根据我们在英特尔社区中发现的一些讨论,以太网calble会导致大量能耗。我们的系统可以很好地证明这一点。因此,我们选择使用串口来控制 FPGA。我们首先在插入以太网电缆的情况下运行功率估算器。如图 8 所示,这种情况下的平均功率约为 7.72W。
然后,我们断开以太网电缆,通过断定串行接口的信号来校准读数。如图 9 所示,平均功率立即下降到 6.24W。我们多次重复这一过程以确认我们的发现,每次断开以太网电缆时,我们总能看到平均功率下降近 1W。因此,我们可以得出结论,使用以太网连接 FPGA 会显著增加功耗。
我们还尝试在 FPGA 中增加两千个 32 位计数器,以观察功耗的变化,但功耗的变化很难观察到。
在这里插入图片描述
图 8:连接以太网电缆后的读数
图 9:以太网电缆断开时的读数
Code
I2C master code
module i2c_master (
// basic signal
input logic clk,
input logic reset, // active high
// control signals
input logic start,
input logic stop,
// i2c ports
output logic scl,
inout wire sda,
// To HPS
output logic [11:0] data_out,
output logic data_valid
);
//-----------------------------------------------------
// SIGNAL DEFINITION
//-----------------------------------------------------
localparam STATE_IDLE = 4'd0;
localparam STATE_START = 4'd1;
localparam STATE_ADDR = 4'd2;
localparam STATE_SLAVE_ACK = 4'd3;
localparam STATE_DATA_RECEIVE_UPPER = 4'd4;
localparam STATE_MASTER_ACK_UPPER = 4'd5;
localparam STATE_DATA_RECEIVE_LOWER = 4'd6;
localparam STATE_MASTER_ACK_LOWER = 4'd7;
localparam STATE_MASTER_NACK = 4'd8;
localparam STATE_STOP = 4'd9;
localparam STATE_WAIT = 4'd10;
logic [2:0] addr_cnt; // counter for address
logic [2:0] upper_cnt; // counter for upper 8-bit
logic [2:0] lower_cnt; // counter for lower 8-bit
//-----------------------------------------------------
// STATE UPDATE
//-----------------------------------------------------
logic [3:0] state_current;
logic [3:0] state_next;
always @( negedge clk ) begin
if ( reset ) begin
state_current <= STATE_IDLE;
end
else begin
state_current <= state_next;
end
end
//----------------------------------------------------------------------
// STATE TRANSITION
//----------------------------------------------------------------------
always_comb begin
case( state_current )
STATE_IDLE: begin
if ( start ) state_next = STATE_START;
else state_next = STATE_IDLE;
end
STATE_START: begin
state_next = STATE_ADDR;
end
STATE_ADDR: begin
if ( addr_cnt == 3'd7 ) state_next = STATE_SLAVE_ACK;
else state_next = STATE_ADDR;
end
STATE_SLAVE_ACK: begin
if ( !sda ) state_next = STATE_DATA_RECEIVE_UPPER;
else state_next = STATE_IDLE;
end
STATE_DATA_RECEIVE_UPPER: begin
if ( upper_cnt == 3'd7 ) state_next = STATE_MASTER_ACK_UPPER;
else state_next = STATE_DATA_RECEIVE_UPPER;
end
STATE_MASTER_ACK_UPPER: begin
if ( !sda ) state_next = STATE_DATA_RECEIVE_LOWER;
else state_next = STATE_IDLE;
end
STATE_DATA_RECEIVE_LOWER: begin
if ( lower_cnt == 3'd7 && !stop ) state_next = STATE_MASTER_ACK_LOWER;
else if ( lower_cnt == 3'd7 && stop ) state_next = STATE_MASTER_NACK;
else state_next = STATE_DATA_RECEIVE_LOWER;
end
STATE_MASTER_ACK_LOWER: begin
state_next = STATE_DATA_RECEIVE_UPPER;
end
STATE_MASTER_NACK: begin
state_next = STATE_STOP;
end
STATE_STOP: begin
state_next = STATE_WAIT;
end
STATE_WAIT: begin
state_next = STATE_IDLE;
end
default:
state_next = STATE_IDLE;
endcase
end
//----------------------------------------------------------------------
// STATE OUTPUT
//----------------------------------------------------------------------
always_comb begin // sck logic
if ( ( state_current != STATE_IDLE ) && ( state_current != STATE_START )
&& ( state_current != STATE_STOP ) && ( state_current != STATE_WAIT ) ) scl = clk;
else if ( state_current == STATE_STOP ) scl = 1'b0;
else scl = 1'b1;
end
logic master_is_input; // tri-state logic for sda
logic sda_temp;
assign sda = master_is_input ? 1'bz : sda_temp;
always_comb begin
case( state_current )
STATE_IDLE: begin
master_is_input = 1'b0;
end
STATE_START: begin
master_is_input = 1'b0;
end
STATE_ADDR: begin
master_is_input = 1'b0;
end
STATE_SLAVE_ACK: begin
master_is_input = 1'b1;
end
STATE_DATA_RECEIVE_UPPER: begin
master_is_input = 1'b1;
end
STATE_MASTER_ACK_UPPER: begin
master_is_input = 1'b0;
end
STATE_DATA_RECEIVE_LOWER: begin
master_is_input = 1'b1;
end
STATE_MASTER_ACK_LOWER: begin
master_is_input = 1'b0;
end
STATE_MASTER_NACK: begin
master_is_input = 1'b0;
end
STATE_STOP: begin
master_is_input = 1'b0;
end
STATE_WAIT: begin
master_is_input = 1'b0;
end
default: begin
master_is_input = 1'b1;
end
endcase
end
always_comb begin // master output for sda logic
case( state_current )
STATE_IDLE: begin
sda_temp = 1'b1;
end
STATE_START: begin
sda_temp = 1'b0;
end
STATE_ADDR: begin
if ( addr_cnt == 0 ) sda_temp = 1'b1;
else if ( addr_cnt == 1 ) sda_temp = 1'b0;
else if ( addr_cnt == 2 ) sda_temp = 1'b0;
else if ( addr_cnt == 3 ) sda_temp = 1'b1;
else if ( addr_cnt == 4 ) sda_temp = 1'b1;
else if ( addr_cnt == 5 ) sda_temp = 1'b0;
else if ( addr_cnt == 6 ) sda_temp = 1'b1;
else if ( addr_cnt == 7 ) sda_temp = 1'b1;
else sda_temp = 1'b0;
end
STATE_MASTER_ACK_UPPER: begin
sda_temp = 1'b0;
end
STATE_MASTER_ACK_LOWER: begin
sda_temp = 1'b0;
end
STATE_MASTER_NACK: begin
sda_temp = 1'b1;
end
STATE_STOP: begin
sda_temp = 1'b0;
end
STATE_WAIT: begin
sda_temp = 1'b0;
end
default: begin
sda_temp = 1'b0;
end
endcase
end
always_ff @( negedge clk ) begin // data_out store
if ( ( state_current == STATE_DATA_RECEIVE_UPPER ) && ( upper_cnt <= 7 ) && ( upper_cnt >= 4 ) ) begin
data_out <= { data_out[10:0], sda };
end
else if ( state_current == STATE_DATA_RECEIVE_LOWER ) begin
data_out <= { data_out[10:0], sda };
end
end
assign data_valid = ( state_current == STATE_MASTER_ACK_LOWER ) || ( state_current == STATE_MASTER_NACK ); // data valid signal logic
//----------------------------------------------------------------------
// CONTROL SIGNALS
//----------------------------------------------------------------------
always_ff @( negedge clk ) begin
if ( state_current == STATE_ADDR ) addr_cnt <= addr_cnt + 1;
else addr_cnt <= 3'd0;
end
always_ff @( negedge clk ) begin
if ( state_current == STATE_DATA_RECEIVE_UPPER ) upper_cnt <= upper_cnt + 1;
else upper_cnt <= 3'd0;
end
always_ff @( negedge clk ) begin
if ( state_current == STATE_DATA_RECEIVE_LOWER ) lower_cnt <= lower_cnt + 1;
else lower_cnt <= 3'd0;
end
endmodule
Hardware implementation on FPGA
//-----------------------------------------------------
// Segment decoder
//-----------------------------------------------------
module segment_decoder (
input logic clk,
//input logic current_val,
input logic signed [26:0] current,
output logic [6:0] hex0,
output logic [6:0] hex1,
output logic [6:0] hex2, // fraction
output logic [6:0] hex3, // integer
output logic [6:0] hex4 // sign
);
always_ff@ ( posedge clk ) begin
if ( current[26] ) hex4 <= 7'b0111111;
else hex4 <= 7'b1111111;
end
logic signed [4:0] current_int;
assign current_int = current[26:22];
always_ff@ ( posedge clk ) begin
if ( !current[26] ) begin
case( current_int )
5'd1: begin
hex3 <= 7'b1111001;
end
5'd2: begin
hex3 <= 7'b0100100;
end
default: begin
hex3 <= 7'b1000000;
end
endcase
end
else begin
case( current_int )
-5'd1: begin
if (current[21:18]==4'd0) hex3 <= 7'b1111001;
else hex3 <= 7'b1000000;
end
-5'd2: begin
if (current[21:18]==4'd0) hex3 <= 7'b0100100;
else hex3 <= 7'b1111001;
end
default: begin
hex3 <= 7'b1000000;
end
endcase
end
end
always_ff@ ( posedge clk ) begin
case( current[21:18] )
4'd0: begin
hex2 <= 7'b1000000;
hex1 <= 7'b1000000;
hex0 <= 7'b1000000;
end
4'd1: begin
hex2 <= current[26] ? 7'b0010000 : 7'b1000000;
hex1 <= current[26] ? 7'b0110000 : 7'b0000010;
hex0 <= current[26] ? 7'b1111000 : 7'b0110000;
end
4'd2: begin
hex2 <= current[26] ? 7'b0000000 : 7'b1111001;
hex1 <= current[26] ? 7'b1111000 : 7'b0100100;
hex0 <= current[26] ? 7'b0010010 : 7'b0010010;
end
4'd3: begin
hex2 <= current[26] ? 7'b0000000 : 7'b1111001;
hex1 <= current[26] ? 7'b1111001 : 7'b0000000;
hex0 <= current[26] ? 7'b0100100 : 7'b0000000;
end
4'd4: begin
hex2 <= current[26] ? 7'b1111000 : 7'b0100100;
hex1 <= current[26] ? 7'b0010010 : 7'b0010010;
hex0 <= current[26] ? 7'b1000000 : 7'b1000000;
end
4'd5: begin
hex2 <= current[26] ? 7'b0000010 : 7'b0110000;
hex1 <= current[26] ? 7'b0000000 : 7'b1111001;
hex0 <= current[26] ? 7'b1111000 : 7'b0110000;
end
4'd6: begin
hex2 <= current[26] ? 7'b0000010 : 7'b0110000;
hex1 <= current[26] ? 7'b0100100 : 7'b1111000;
hex0 <= current[26] ? 7'b0010010 : 7'b0010010;
end
4'd7: begin
hex2 <= current[26] ? 7'b0010010 : 7'b0011001;
hex1 <= current[26] ? 7'b0000010 : 7'b0110000;
hex0 <= current[26] ? 7'b0100100 : 7'b0000000;
end
4'd8: begin
hex2 <= current[26] ? 7'b0010010 : 7'b0010010;
hex1 <= current[26] ? 7'b1000000 : 7'b1000000;
hex0 <= current[26] ? 7'b1000000 : 7'b1000000;
end
4'd9: begin
hex2 <= current[26] ? 7'b0011001 : 7'b0010010;
hex1 <= current[26] ? 7'b0110000 : 7'b0000010;
hex0 <= current[26] ? 7'b1111000 : 7'b0110000;
end
4'd10: begin
hex2 <= current[26] ? 7'b0110000 : 7'b0000010;
hex1 <= current[26] ? 7'b1111000 : 7'b0100100;
hex0 <= current[26] ? 7'b0010010 : 7'b0010010;
end
4'd11: begin
hex2 <= current[26] ? 7'b0110000 : 7'b0000010;
hex1 <= current[26] ? 7'b1111001 : 7'b0000000;
hex0 <= current[26] ? 7'b0100100 : 7'b0000000;
end
4'd12: begin
hex2 <= current[26] ? 7'b0100100 : 7'b1111000;
hex1 <= current[26] ? 7'b0010010 : 7'b0010010;
hex0 <= current[26] ? 7'b1000000 : 7'b1000000;
end
4'd13: begin
hex2 <= current[26] ? 7'b1111001 : 7'b0000000;
hex1 <= current[26] ? 7'b0000000 : 7'b1111001;
hex0 <= current[26] ? 7'b1111000 : 7'b0110000;
end
4'd14: begin
hex2 <= current[26] ? 7'b1111001 : 7'b0000000;
hex1 <= current[26] ? 7'b0100100 : 7'b1111000;
hex0 <= current[26] ? 7'b0010010 : 7'b0010010;
end
4'd15: begin
hex2 <= current[26] ? 7'b1000000 : 7'b0010000;
hex1 <= current[26] ? 7'b0000010 : 7'b0110000;
hex0 <= current[26] ? 7'b0100100 : 7'b0000000;
end
endcase
end
endmodule
//-----------------------------------------------------
// Fixed point multipliers
//-----------------------------------------------------
module signed_mult_27bit
(
input logic signed [26:0] a,
input logic signed [26:0] b,
output logic signed [26:0] out
);
logic signed [53:0] mult_out;
assign mult_out = a * b;
assign out = {mult_out[53], mult_out[47:22]};
endmodule
module signed_mult_32bit
(
input logic signed [31:0] a,
input logic signed [31:0] b,
output logic signed [31:0] out
);
logic signed [63:0] mult_out;
assign mult_out = a * b;
assign out = {mult_out[63], mult_out[53:23]};
endmodule
//-----------------------------------------------------
// 200kHz clock
//-----------------------------------------------------
module clk_divider_400k(
input logic reset,
input logic clk_50M,
output logic clk_400k
);
logic [6:0] counter;
always_ff@( posedge clk_50M ) begin
if ( reset ) begin
clk_400k <= 1'b1;
counter <= 0;
end
else if ( counter == 124 ) begin
clk_400k <= !clk_400k;
counter <= 0;
end
else begin
counter <= counter + 1;
end
end
endmodule
//-----------------------------------------------------
// Integrater
//-----------------------------------------------------
module integrater(
// basic
input logic reset,
input logic clk,
// control: signals to start and clear integration
input logic data_valid, // = data_valid from i2c interface
input logic start,
input logic stop,
input logic cali,
// From I2C interface
input logic signed [26:0] input_data, // 5.22
// To HPS
output logic signed [63:0] energy_out,
output logic [31:0] cycle_cnt
);
// PARAMETER DEFINITION
logic signed [31:0] unit_voltage = 32'd9059; // (2^23)*18*12*1/200000
logic signed [31:0] input_data_32bit; // 9.23
assign input_data_32bit = { {4{input_data[26]}}, input_data, 1'b0};
logic signed [31:0] unit_energy;
// UNIT ENERGY
signed_mult_32bit multiply(
.a(unit_voltage),
.b(input_data_32bit),
.out(unit_energy)
);
// CONTROL
logic out_ind;
always_ff @( posedge clk or posedge reset or posedge cali ) begin
if ( reset || cali ) out_ind <= 1'b0;
else if ( stop ) out_ind <= 1'b1;
else if ( start ) out_ind <= 1'b0;
end
// ENERGY COMPUTATION
always_ff @( posedge clk or posedge reset or posedge cali ) begin
if ( reset || cali ) energy_out <= 32'd0;
else if ( !out_ind && data_valid ) energy_out <= energy_out + { {32{unit_energy[31]}}, unit_energy};
end
// TIME COUNTER
always_ff @( posedge clk or posedge reset or posedge cali ) begin
if ( reset || cali ) cycle_cnt <= 32'd0;
else if ( !out_ind && data_valid ) cycle_cnt <= cycle_cnt + 1;
end
endmodule
C code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <math.h>
#include <string.h>
#include <errno.h> // Definition for "error handling"
#include <sys/wait.h> // Definition for wait()
//#include "address_map_arm_brl4.h"
// threads
#include <pthread.h>
/****************************************************************************************
* Prameter Definition
****************************************************************************************/
//============= VGA PLOT PARAMS ========================
/* Cyclone V FPGA devices */
#define HW_REGS_BASE 0xff200000
//#define HW_REGS_SPAN 0x00200000
#define HW_REGS_SPAN 0x00005000
#define FPGA_ONCHIP_BASE 0xC8000000