Source / Read the circuit
ti_adc_backend.sv
lessons/adc_digital/rtl/ti_adc_backend.sv
`timescale 1ns/1ps
// AD06/07/15/17: four-lane teaching backend, core100MHz, sample every25 cycles.
// Sample and response ports describe events accepted at the next rising edge.
module ti_adc_backend(input wire clk, rst_n,
input wire [15:0] epoch,
output wire [3:0] sample_req,
output wire [31:0] sample_id,
input wire [3:0] rsp_valid,
input wire [47:0] rsp_raw,
input wire [127:0] rsp_id,
input wire [63:0] rsp_epoch,
input wire cfg_commit,
input wire [71:0] cfg_offsets, cfg_gains,
output reg cfg_accepted, cfg_rejected, cfg_applied,
output reg [31:0] applied_id,
input wire sink_ready,
output wire out_valid, out_sat,
output wire signed [17:0] out_data,
output wire [65:0] out_meta,
output reg fault,
output reg [3:0] fault_code,
output reg [31:0] fault_id,
output reg [63:0] fault_tick,
input wire snapshot,
output reg [191:0] stats_snapshot);
reg [63:0] tick;
reg [4:0] phase;
reg [31:0] next_id, release_id;
reg [15:0] version;
reg pending;
reg signed [17:0] active_o[0:3], active_g[0:3], pend_o[0:3], pend_g[0:3];
reg occupied[0:7], received[0:7];
reg [31:0] tags[0:7];
// A slot lives <=80 cycles, is reused after200, and faults before256.
// Full tags/epoch retain identity; only local age is modulo256.
reg [7:0] times[0:7];
reg signed [11:0] raw_slots[0:7];
reg signed [17:0] off_slots[0:7], gain_slots[0:7];
reg [15:0] versions[0:7];
reg [63:0] requests, responses, delivered;
wire [2:0] alloc_slot=next_id[2:0], release_slot=release_id[2:0];
wire sample_event=(phase==0) && !fault && rst_n;
wire [7:0] release_age=tick[7:0]-times[release_slot];
wire due=occupied[release_slot] && (release_age==8'd80);
reg bad;
reg [3:0] bad_code;
reg [31:0] bad_id;
reg cfg_ok;
reg signed [17:0] check_o, check_g;
integer i;
reg [2:0] s, response_count;
reg [7:0] age;
wire corr_valid, corr_sat;
wire signed [17:0] corr_data;
wire [65:0] corr_meta;
always @* begin
bad=0; bad_code=0; bad_id=next_id; response_count=0;
cfg_ok=1; check_o=0; check_g=0; s=0; age=0;
for(i=0;i<4;i=i+1) begin
check_o=$signed(cfg_offsets[i*18+:18]); check_g=$signed(cfg_gains[i*18+:18]);
if(check_o< -18'sd2048 || check_o>18'sd2048 ||
check_g<18'sd52429 || check_g>18'sd81920) cfg_ok=0;
// Slot j belongs to lane j%4: each lane can target only two slots.
s={rsp_id[i*32+2],i[1:0]};
age=tick[7:0]-times[s];
if(rsp_valid[i]) begin
response_count=response_count+1;
if(!occupied[s] || tags[s]!=rsp_id[i*32+:32] ||
rsp_id[i*32+:2]!=i || rsp_epoch[i*16+:16]!=epoch) begin
bad=1; bad_code=1; bad_id=rsp_id[i*32+:32];
end else if(received[s]) begin bad=1; bad_code=2; bad_id=tags[s]; end
else if(age<8'd1 || age>8'd60) begin
bad=1; bad_code=3; bad_id=tags[s];
end
end
end
if(due && !received[release_slot]) begin bad=1; bad_code=4; bad_id=release_id; end
if(sample_event && occupied[alloc_slot]) begin bad=1; bad_code=5; bad_id=next_id; end
if(corr_valid && !sink_ready) begin bad=1; bad_code=6; bad_id=corr_meta[33:2]; end
if(sample_event && (next_id==32'hfffffffc ||
(next_id[1:0]==0 && pending && version==16'hffff))) begin
bad=1; bad_code=7; bad_id=next_id;
end
end
assign sample_req = sample_event && !bad ? (4'b0001<<next_id[1:0]) : 4'b0000;
assign sample_id=next_id;
// fault is registered. Gating with live response validation would glitch
// after an accepted response changes its slot from empty to received.
assign out_valid=corr_valid && !fault;
assign out_data=corr_data; assign out_sat=corr_sat; assign out_meta=corr_meta;
adc_fixed_correct correction(.clk(clk),.rst_n(rst_n),.flush(fault||bad),
.in_valid(due && !fault && !bad),.raw(raw_slots[release_slot]),
.offset(off_slots[release_slot]),.inverse_gain(gain_slots[release_slot]),
.meta({epoch,versions[release_slot],tags[release_slot],tags[release_slot][1:0]}),
.out_valid(corr_valid),.sat(corr_sat),.data(corr_data),.out_meta(corr_meta));
integer j;
always @(posedge clk) begin
if(!rst_n) begin
tick<=0; phase<=0; next_id<=0; release_id<=0; version<=0; pending<=0;
fault<=0; fault_code<=0; fault_id<=0; fault_tick<=0;
cfg_accepted<=0; cfg_rejected<=0; cfg_applied<=0; applied_id<=0;
requests<=0; responses<=0; delivered<=0; stats_snapshot<=0;
for(j=0;j<4;j=j+1) begin active_o[j]<=0; active_g[j]<=18'sd65536;
pend_o[j]<=0; pend_g[j]<=18'sd65536; end
for(j=0;j<8;j=j+1) begin occupied[j]<=0; received[j]<=0; tags[j]<=0;
times[j]<=0; raw_slots[j]<=0; off_slots[j]<=0; gain_slots[j]<=0; versions[j]<=0; end
end else begin
cfg_accepted<=0; cfg_rejected<=0; cfg_applied<=0;
if(snapshot) stats_snapshot<={requests,responses,delivered};
if(!fault) begin
if(bad) begin
fault<=1; fault_code<=bad_code; fault_id<=bad_id; fault_tick<=tick;
cfg_rejected<=cfg_commit;
for(j=0;j<8;j=j+1) begin occupied[j]<=0; received[j]<=0; end
end else begin
tick<=tick+1'b1;
phase<=phase==24 ? 0 : phase+1'b1;
if(cfg_commit) begin
if(pending || !cfg_ok) cfg_rejected<=1;
else begin pending<=1; cfg_accepted<=1;
for(j=0;j<4;j=j+1) begin pend_o[j]<=cfg_offsets[j*18+:18];
pend_g[j]<=cfg_gains[j*18+:18]; end
end
end
if(sample_event) begin
requests<=requests+1'b1; next_id<=next_id+1'b1;
occupied[alloc_slot]<=1; received[alloc_slot]<=0;
tags[alloc_slot]<=next_id; times[alloc_slot]<=tick[7:0];
if(next_id[1:0]==0 && pending) begin
pending<=0; version<=version+1'b1; cfg_applied<=1; applied_id<=next_id;
for(j=0;j<4;j=j+1) begin active_o[j]<=pend_o[j]; active_g[j]<=pend_g[j]; end
off_slots[alloc_slot]<=pend_o[next_id[1:0]];
gain_slots[alloc_slot]<=pend_g[next_id[1:0]];
versions[alloc_slot]<=version+1'b1;
end else begin off_slots[alloc_slot]<=active_o[next_id[1:0]];
gain_slots[alloc_slot]<=active_g[next_id[1:0]];
versions[alloc_slot]<=version; end
end
responses<=responses+response_count;
// Constant lane per slot avoids inferring four arbitrary
// writers. Wrong-lane/tag responses already enter HALT.
for(j=0;j<8;j=j+1) if(rsp_valid[j%4] && rsp_id[(j%4)*32+:3]==j) begin
received[j]<=1;
raw_slots[j]<=rsp_raw[(j%4)*12+:12];
end
if(due) begin occupied[release_slot]<=0; received[release_slot]<=0;
release_id<=release_id+1'b1; end
if(out_valid) delivered<=delivered+1'b1;
end
end else if(cfg_commit) cfg_rejected<=1;
end
end
endmodule