blob: ebe3a21ebf4cdd32b5b3249ee098ceb7c305f145 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// adc_ctrl core module
module adc_ctrl_core import adc_ctrl_reg_pkg::* ; (
input clk_aon_i,//Always-on 200KHz clock(logic)
input rst_aon_ni,//power-on reset for the 200KHz clock(logic)
input clk_i,//regular core clock for SW config interface
input rst_ni,//power-on hardware reset
// register interface inputs
input adc_ctrl_reg2hw_t reg2hw_i,
// register interface outputs
output adc_ctrl_hw2reg_intr_state_reg_t intr_state_o,
output adc_ctrl_hw2reg_adc_chn_val_mreg_t [NumAdcChannel-1:0] adc_chn_val_o,
output adc_ctrl_hw2reg_adc_intr_status_reg_t adc_intr_status_o,
output adc_ctrl_hw2reg_filter_status_reg_t aon_filter_status_o,
// interrupt and wakeup outputs
output wkup_req_o,
output intr_o,
// adc interface
input ast_pkg::adc_ast_rsp_t adc_i,
output ast_pkg::adc_ast_req_t adc_o
);
logic chn0_val_we, chn1_val_we;//write enable for the latest ADC sample
logic [9:0] chn0_val, chn1_val;
logic [NumAdcFilter-1:0] chn0_match, chn1_match, match;
logic [NumAdcFilter-1:0] match_pulse;
//write enable for the ADC sample when the interrupt is triggered
logic adc_ctrl_done, oneshot_done;
// Pack channel 0/1 into one variable
typedef struct packed {
logic [9:0] min_v;
logic [9:0] max_v;
logic cond;
logic en;
} filter_ctl_t;
filter_ctl_t [NumAdcChannel-1:0][NumAdcFilter-1:0] aon_filter_ctl;
for (genvar k = 0 ; k < NumAdcFilter ; k++) begin : gen_filter_ctl_sync
assign aon_filter_ctl[0][k] = '{
min_v: reg2hw_i.adc_chn0_filter_ctl[k].min_v.q,
max_v: reg2hw_i.adc_chn0_filter_ctl[k].max_v.q,
cond: reg2hw_i.adc_chn0_filter_ctl[k].cond.q,
en: reg2hw_i.adc_chn0_filter_ctl[k].en.q
};
assign aon_filter_ctl[1][k] = '{
min_v: reg2hw_i.adc_chn1_filter_ctl[k].min_v.q,
max_v: reg2hw_i.adc_chn1_filter_ctl[k].max_v.q,
cond: reg2hw_i.adc_chn1_filter_ctl[k].cond.q,
en: reg2hw_i.adc_chn1_filter_ctl[k].en.q
};
end // block: gen_filter_ctl_sync
// Recent adc channel values
assign adc_chn_val_o[0].adc_chn_value.de = chn0_val_we;
assign adc_chn_val_o[0].adc_chn_value.d = chn0_val;
assign adc_chn_val_o[1].adc_chn_value.de = chn1_val_we;
assign adc_chn_val_o[1].adc_chn_value.d = chn1_val;
// Interrupt based adc channel values
// The value of the adc is captured whenever an interrupt triggers.
// There are two cases:
// completion of one shot mode
// match detection from the filters
logic chn_val_intr_we;
assign chn_val_intr_we = reg2hw_i.adc_en_ctl.oneshot_mode.q ? oneshot_done :
reg2hw_i.adc_en_ctl.adc_enable.q ? |match_pulse : '0;
assign adc_chn_val_o[0].adc_chn_value_intr.de = chn_val_intr_we;
assign adc_chn_val_o[0].adc_chn_value_intr.d = chn0_val;
assign adc_chn_val_o[1].adc_chn_value_intr.de = chn_val_intr_we;
assign adc_chn_val_o[1].adc_chn_value_intr.d = chn1_val;
//Connect the ports for future extension
assign adc_chn_val_o[0].adc_chn_value_ext.de = 1'b0;
assign adc_chn_val_o[0].adc_chn_value_ext.d = 2'b0;
assign adc_chn_val_o[1].adc_chn_value_ext.de = 1'b0;
assign adc_chn_val_o[1].adc_chn_value_ext.d = 2'b0;
assign adc_chn_val_o[0].adc_chn_value_intr_ext.de = 1'b0;
assign adc_chn_val_o[0].adc_chn_value_intr_ext.d = 2'b0;
assign adc_chn_val_o[1].adc_chn_value_intr_ext.de = 1'b0;
assign adc_chn_val_o[1].adc_chn_value_intr_ext.d = 2'b0;
//Evaluate if there is a match from chn0 and chn1 samples
for (genvar k = 0 ; k < NumAdcFilter ; k++) begin : gen_filter_match
assign chn0_match[k] = (!aon_filter_ctl[0][k].cond) ?
(aon_filter_ctl[0][k].min_v <= chn0_val) && (chn0_val <= aon_filter_ctl[0][k].max_v) :
(aon_filter_ctl[0][k].min_v > chn0_val) || (chn0_val > aon_filter_ctl[0][k].max_v);
assign chn1_match[k] = (!aon_filter_ctl[1][k].cond) ?
(aon_filter_ctl[1][k].min_v <= chn1_val) && (chn1_val <= aon_filter_ctl[1][k].max_v) :
(aon_filter_ctl[1][k].min_v > chn1_val) || (chn1_val > aon_filter_ctl[1][k].max_v);
// If the filter on a paritcular channel is NOT enabled, it does not participate in the final
// match decision. This means the match value should have no impact on the final result.
// For example, if channel 0's filter is enabled, but channel 1's is not, the match result
// is determined solely based on whether channel 0's filter shows a match.
// On the other hand, if all channel's filters are enabled, then a match is seen only when
// both filters match.
assign match[k] = |{aon_filter_ctl[0][k].en, aon_filter_ctl[1][k].en} &
(!aon_filter_ctl[0][k].en | (chn0_match[k] & aon_filter_ctl[0][k].en)) &
(!aon_filter_ctl[1][k].en | (chn1_match[k] & aon_filter_ctl[1][k].en)) ;
assign match_pulse[k] = adc_ctrl_done && match[k];
// Explicitly create assertions for all the matching conditions.
// These assertions are unwieldly and not suitable for expansion to more channels.
// They should be adjusted eventually.
`ASSERT(MatchCheck00_A, !aon_filter_ctl[0][k].en & !aon_filter_ctl[1][k].en |->
!match[k], clk_aon_i, !rst_aon_ni)
`ASSERT(MatchCheck01_A, !aon_filter_ctl[0][k].en & aon_filter_ctl[1][k].en |->
match[k] == chn1_match[k], clk_aon_i, !rst_aon_ni)
`ASSERT(MatchCheck10_A, aon_filter_ctl[0][k].en & !aon_filter_ctl[1][k].en |->
match[k] == chn0_match[k], clk_aon_i, !rst_aon_ni)
`ASSERT(MatchCheck11_A, aon_filter_ctl[0][k].en & aon_filter_ctl[1][k].en |->
match[k] == (chn0_match[k] & chn1_match[k]), clk_aon_i, !rst_aon_ni)
end
// adc filter status
assign aon_filter_status_o.d = match_pulse | reg2hw_i.filter_status.q;
assign aon_filter_status_o.de = |match_pulse;
// generate wakeup to external power manager if filter status
// and wakeup enable are set.
assign wkup_req_o = |(reg2hw_i.filter_status.q &
reg2hw_i.adc_wakeup_ctl.q);
//instantiate the main state machine
adc_ctrl_fsm u_adc_ctrl_fsm (
.clk_aon_i,
.rst_aon_ni,
// configuration and settings from reg interface
.cfg_fsm_rst_i(reg2hw_i.adc_fsm_rst.q),
.cfg_adc_enable_i(reg2hw_i.adc_en_ctl.adc_enable.q),
.cfg_oneshot_mode_i(reg2hw_i.adc_en_ctl.oneshot_mode.q),
.cfg_lp_mode_i(reg2hw_i.adc_pd_ctl.lp_mode.q),
.cfg_pwrup_time_i(reg2hw_i.adc_pd_ctl.pwrup_time.q),
.cfg_wakeup_time_i(reg2hw_i.adc_pd_ctl.wakeup_time.q),
.cfg_lp_sample_cnt_i(reg2hw_i.adc_lp_sample_ctl.q),
.cfg_np_sample_cnt_i(reg2hw_i.adc_sample_ctl.q),
//
.adc_ctrl_match_i(match),
.adc_d_i(adc_i.data),
.adc_d_val_i(adc_i.data_valid),
.adc_pd_o(adc_o.pd),
.adc_chn_sel_o(adc_o.channel_sel),
.chn0_val_we_o(chn0_val_we),
.chn1_val_we_o(chn1_val_we),
.chn0_val_o(chn0_val),
.chn1_val_o(chn1_val),
.adc_ctrl_done_o(adc_ctrl_done),
.oneshot_done_o(oneshot_done)
);
// synchronzie from clk_aon into cfg domain
logic cfg_oneshot_done;
prim_pulse_sync u_oneshot_done_sync (
.clk_src_i(clk_aon_i),
.rst_src_ni(rst_aon_ni),
.src_pulse_i(oneshot_done),
.clk_dst_i(clk_i),
.rst_dst_ni(rst_ni),
.dst_pulse_o(cfg_oneshot_done)
);
//Instantiate the interrupt module
adc_ctrl_intr u_adc_ctrl_intr (
.clk_aon_i,
.rst_aon_ni,
.aon_filter_match_i(match_pulse),
.clk_i,
.rst_ni,
.cfg_intr_en_i(reg2hw_i.adc_intr_ctl.q),
.cfg_oneshot_done_i(cfg_oneshot_done),
.intr_state_i(reg2hw_i.intr_state),
.intr_enable_i(reg2hw_i.intr_enable),
.intr_test_i(reg2hw_i.intr_test),
.intr_state_o,
.adc_intr_status_i(reg2hw_i.adc_intr_status),
.adc_intr_status_o,
.intr_o
);
// unused register inputs
logic unused_cfgs;
assign unused_cfgs = ^reg2hw_i;
//////////////////////
// Assertions
//////////////////////
`ASSERT(MaxFilters_A, NumAdcFilter <= 32, clk_aon_i, !rst_aon_ni)
endmodule