Merge pull request 'dev/controller' (#5) from dev/controller into master
Reviewed-on: #5
This commit is contained in:
62
rtl/controller/README.md
Normal file
62
rtl/controller/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Системный контроллер
|
||||
|
||||
Контроллер принимает входные пакеты udp с ethernet, передаваемые по axi stream, и выполняет настройку выходных регистров в соотвествии с содержимым этого пакета, а также синхронизирует сигналы между тремя clock domains - есть clk от ethernet, clk для ЦАП и clk для АЦП
|
||||
|
||||
## Список параметров:
|
||||
- dac_data_width - битность данных ЦАП, <= 16bit
|
||||
|
||||
## Список входных портов:
|
||||
- eth_clk_in - базовая входная частота
|
||||
- dac_clk_in - входная частота ЦАП
|
||||
- adc_clk_in - входная частота АЦП
|
||||
- rst_n - общий reset
|
||||
- s_axis [8 bit] - AXI stream slave для приема данных от ethernet udp (уже разобранный payload по байтам) - домен eth_clk
|
||||
- finish - сигнал окончания приема данных с АЦП, домен adc_clk !
|
||||
|
||||
## Список выходных портов:
|
||||
- dac_pulse_width[31:0] - выход pulse_width в домене dac_clk
|
||||
- dac_pulse_period[31:0] - выход pulse_period в домене dac_clk
|
||||
- dac_pulse_height[dac_data_width-1:0] - выход pulse_height в домене dac_clk
|
||||
- dac_pulse_num[15:0] - выход pulse_num в домене dac_clk
|
||||
---
|
||||
- adc_pulse_period[31:0] - выход pulse_period в домене adc_clk
|
||||
- adc_pulse_num[15:0] - выход pulse_num в домене adc_clk
|
||||
---
|
||||
- dac_start - start в домене dac_clk
|
||||
- adc_start - start в домене adc_clk
|
||||
---
|
||||
- dac_rst - rst в домене dac_clk
|
||||
- adc_rst - rst в домене adc_clk
|
||||
|
||||
## Логика работы:
|
||||
по умолчанию после инициализации блок встает в состояние ожидания (*idle*), и становится *ready* для приема данных по axis.
|
||||
далее ждет контрольный пакет. всего есть 3 вариации контрольных пакетов (в любом порядке), получаемых по axi stream:
|
||||
```
|
||||
8'b00001111 - soft reset
|
||||
8'b11110000 - start
|
||||
8'b10001000 - set_data
|
||||
```
|
||||
|
||||
*soft reset* отправляет пульс rst на dac_rst и adc_rst, синхронизировав пульсы в их доменах. при этом сброс самого контроллера не происходит, значения остаются как и были
|
||||
|
||||
*start* отправляет пульс start на dac_start и adc_start в их доменах. при этом после этого блок перестает быть ready и ждет, пока не придет пульс finish, после этого он возвращается снова в *idle* состояние
|
||||
|
||||
*set_data* значит, что следующие 96 бит = 12*8 байт, пришедшии по axis - это конфигурационная информация и ее нужно записать в внутренний регистр на 96 бит.
|
||||
|
||||
конфигурационный регистр на 96 бит делится так:
|
||||
```
|
||||
reg[31:0] - pulse_width
|
||||
reg[63:32] - pulse_period
|
||||
reg[79:64] - pulse_num
|
||||
reg[79+dac_data_width:80] - pulse_height
|
||||
```
|
||||
|
||||
соотвественно эти записанные значения выставляются на соотвествующие выходные сигналы в доменах dac_clk и adc_clk. выходы обновляются каждый раз, когда происходит set_data, и сигналы сохраняют своё значение до следующего set_data.
|
||||
|
||||
## Симуляция
|
||||
Тесты запускаются автоматически через make.
|
||||
```
|
||||
cd tests
|
||||
make sim
|
||||
```
|
||||
Должно выдать "All tests done" в конце симуляции.
|
||||
462
rtl/controller/src/controller.sv
Normal file
462
rtl/controller/src/controller.sv
Normal file
@ -0,0 +1,462 @@
|
||||
module control #(
|
||||
parameter int unsigned DAC_DATA_WIDTH = 12
|
||||
) (
|
||||
input logic eth_clk_in,
|
||||
input logic dac_clk_in,
|
||||
input logic adc_clk_in,
|
||||
input logic rst_n,
|
||||
|
||||
// AXI stream slave, eth_clk_in domain
|
||||
input logic [7:0] s_axis_tdata,
|
||||
input logic s_axis_tvalid,
|
||||
output logic s_axis_tready,
|
||||
input logic s_axis_tlast,
|
||||
|
||||
// adc_clk_in domain
|
||||
input logic finish,
|
||||
|
||||
// dac_clk_in domain outputs
|
||||
output logic [31:0] dac_pulse_width,
|
||||
output logic [31:0] dac_pulse_period,
|
||||
output logic [DAC_DATA_WIDTH-1:0] dac_pulse_height,
|
||||
output logic [15:0] dac_pulse_num,
|
||||
|
||||
// adc_clk_in domain outputs
|
||||
output logic [31:0] adc_pulse_period,
|
||||
output logic [15:0] adc_pulse_num,
|
||||
|
||||
// pulse outputs
|
||||
output logic dac_start,
|
||||
output logic adc_start,
|
||||
output logic dac_rst,
|
||||
output logic adc_rst
|
||||
);
|
||||
|
||||
// static checks
|
||||
initial begin
|
||||
if (DAC_DATA_WIDTH > 16) begin
|
||||
$error("DAC_DATA_WIDTH must be <= 16");
|
||||
end
|
||||
if (DAC_DATA_WIDTH == 0) begin
|
||||
$error("DAC_DATA_WIDTH must be > 0");
|
||||
end
|
||||
end
|
||||
|
||||
// command constants
|
||||
localparam logic [7:0] CMD_SOFT_RESET = 8'h0F;
|
||||
localparam logic [7:0] CMD_START = 8'hF0;
|
||||
localparam logic [7:0] CMD_SET_DATA = 8'h88;
|
||||
|
||||
// reset synchronizers: async assert, sync deassert in each domain
|
||||
logic eth_rst_ff1, eth_rst_ff2;
|
||||
logic dac_rst_ff1, dac_rst_ff2;
|
||||
logic adc_rst_ff1, adc_rst_ff2;
|
||||
|
||||
logic eth_rst;
|
||||
logic dac_rst_int;
|
||||
logic adc_rst_int;
|
||||
|
||||
always_ff @(posedge eth_clk_in or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
eth_rst_ff1 <= 1'b1;
|
||||
eth_rst_ff2 <= 1'b1;
|
||||
end else begin
|
||||
eth_rst_ff1 <= 1'b0;
|
||||
eth_rst_ff2 <= eth_rst_ff1;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge dac_clk_in or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
dac_rst_ff1 <= 1'b1;
|
||||
dac_rst_ff2 <= 1'b1;
|
||||
end else begin
|
||||
dac_rst_ff1 <= 1'b0;
|
||||
dac_rst_ff2 <= dac_rst_ff1;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge adc_clk_in or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
adc_rst_ff1 <= 1'b1;
|
||||
adc_rst_ff2 <= 1'b1;
|
||||
end else begin
|
||||
adc_rst_ff1 <= 1'b0;
|
||||
adc_rst_ff2 <= adc_rst_ff1;
|
||||
end
|
||||
end
|
||||
|
||||
assign eth_rst = eth_rst_ff2;
|
||||
assign dac_rst_int = dac_rst_ff2;
|
||||
assign adc_rst_int = adc_rst_ff2;
|
||||
|
||||
// axi stream is always accepted. If packet is not needed, it is discarded.
|
||||
assign s_axis_tready = 1'b1;
|
||||
|
||||
(* MARK_DEBUG="true" *) wire axis_hs = s_axis_tvalid & s_axis_tready;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Shared 96-bit config bus in ETH domain
|
||||
//
|
||||
// Byte order for SET_DATA payload, little-endian:
|
||||
// payload byte 0 -> cfg_bus_eth[7:0]
|
||||
// payload byte 1 -> cfg_bus_eth[15:8]
|
||||
// ...etc...
|
||||
// payload byte 11 -> cfg_bus_eth[95:88]
|
||||
//
|
||||
// Field layout inside cfg_bus_eth:
|
||||
// [31:0] pulse_width
|
||||
// [63:32] pulse_period
|
||||
// [79:64] pulse_num
|
||||
// [95:80] pulse_height_raw[15:0]
|
||||
// -------------------------------------------------------------------------
|
||||
(* MARK_DEBUG="true" *) logic [95:0] cfg_bus_eth;
|
||||
logic [95:0] cfg_shift_eth;
|
||||
|
||||
// ETH-domain parser and control
|
||||
typedef enum logic [2:0] {
|
||||
ST_IDLE = 3'd0,
|
||||
ST_RECV_CFG = 3'd1,
|
||||
ST_WAIT_CFG_ACK = 3'd2,
|
||||
ST_DISCARD = 3'd3
|
||||
} eth_state_t;
|
||||
|
||||
(* MARK_DEBUG="true" *) eth_state_t eth_state;
|
||||
|
||||
logic [3:0] cfg_byte_cnt;
|
||||
|
||||
// Busy flag: set by START command, cleared by finish event from ADC domain
|
||||
(* MARK_DEBUG="true" *) logic busy_flag_eth;
|
||||
|
||||
// Pending ACKs for config delivery
|
||||
logic cfg_wait_dac_ack;
|
||||
logic cfg_wait_adc_ack;
|
||||
|
||||
// Event toggles ETH -> DAC/ADC
|
||||
logic start_toggle_eth;
|
||||
logic rst_toggle_eth;
|
||||
|
||||
// Config request toggles ETH -> DAC/ADC
|
||||
logic cfg_req_toggle_dac_eth;
|
||||
logic cfg_req_toggle_adc_eth;
|
||||
|
||||
// ACK toggles DAC/ADC -> ETH
|
||||
logic cfg_ack_toggle_dac;
|
||||
logic cfg_ack_toggle_adc;
|
||||
|
||||
(* ASYNC_REG = "TRUE" *) logic cfg_ack_toggle_dac_meta, cfg_ack_toggle_dac_sync, cfg_ack_toggle_dac_sync_d;
|
||||
(* ASYNC_REG = "TRUE" *) logic cfg_ack_toggle_adc_meta, cfg_ack_toggle_adc_sync, cfg_ack_toggle_adc_sync_d;
|
||||
|
||||
wire cfg_ack_pulse_dac_eth = cfg_ack_toggle_dac_sync ^ cfg_ack_toggle_dac_sync_d;
|
||||
wire cfg_ack_pulse_adc_eth = cfg_ack_toggle_adc_sync ^ cfg_ack_toggle_adc_sync_d;
|
||||
|
||||
always_ff @(posedge eth_clk_in or posedge eth_rst) begin
|
||||
if (eth_rst) begin
|
||||
cfg_ack_toggle_dac_meta <= 1'b0;
|
||||
cfg_ack_toggle_dac_sync <= 1'b0;
|
||||
cfg_ack_toggle_dac_sync_d <= 1'b0;
|
||||
|
||||
cfg_ack_toggle_adc_meta <= 1'b0;
|
||||
cfg_ack_toggle_adc_sync <= 1'b0;
|
||||
cfg_ack_toggle_adc_sync_d <= 1'b0;
|
||||
end else begin
|
||||
cfg_ack_toggle_dac_meta <= cfg_ack_toggle_dac;
|
||||
cfg_ack_toggle_dac_sync <= cfg_ack_toggle_dac_meta;
|
||||
cfg_ack_toggle_dac_sync_d <= cfg_ack_toggle_dac_sync;
|
||||
|
||||
cfg_ack_toggle_adc_meta <= cfg_ack_toggle_adc;
|
||||
cfg_ack_toggle_adc_sync <= cfg_ack_toggle_adc_meta;
|
||||
cfg_ack_toggle_adc_sync_d <= cfg_ack_toggle_adc_sync;
|
||||
end
|
||||
end
|
||||
|
||||
// finish event: ADC -> ETH via toggle CDC
|
||||
logic finish_toggle_adc;
|
||||
logic finish_meta_eth, finish_sync_eth, finish_sync_eth_d;
|
||||
|
||||
wire finish_pulse_eth = finish_sync_eth ^ finish_sync_eth_d;
|
||||
|
||||
always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin
|
||||
if (adc_rst_int) begin
|
||||
finish_toggle_adc <= 1'b0;
|
||||
end else if (finish) begin
|
||||
finish_toggle_adc <= ~finish_toggle_adc;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge eth_clk_in or posedge eth_rst) begin
|
||||
if (eth_rst) begin
|
||||
finish_meta_eth <= 1'b0;
|
||||
finish_sync_eth <= 1'b0;
|
||||
finish_sync_eth_d <= 1'b0;
|
||||
end else begin
|
||||
finish_meta_eth <= finish_toggle_adc;
|
||||
finish_sync_eth <= finish_meta_eth;
|
||||
finish_sync_eth_d <= finish_sync_eth;
|
||||
end
|
||||
end
|
||||
|
||||
// ETH FSM
|
||||
always_ff @(posedge eth_clk_in or posedge eth_rst) begin
|
||||
if (eth_rst) begin
|
||||
eth_state <= ST_IDLE;
|
||||
cfg_byte_cnt <= '0;
|
||||
cfg_shift_eth <= '0;
|
||||
cfg_bus_eth <= '0;
|
||||
|
||||
busy_flag_eth <= 1'b0;
|
||||
|
||||
start_toggle_eth <= 1'b0;
|
||||
rst_toggle_eth <= 1'b0;
|
||||
|
||||
cfg_req_toggle_dac_eth <= 1'b0;
|
||||
cfg_req_toggle_adc_eth <= 1'b0;
|
||||
|
||||
cfg_wait_dac_ack <= 1'b0;
|
||||
cfg_wait_adc_ack <= 1'b0;
|
||||
end else begin
|
||||
// finish always clears busy
|
||||
if (finish_pulse_eth) begin
|
||||
busy_flag_eth <= 1'b0;
|
||||
end
|
||||
|
||||
// config acks
|
||||
if (cfg_ack_pulse_dac_eth) begin
|
||||
cfg_wait_dac_ack <= 1'b0;
|
||||
end
|
||||
if (cfg_ack_pulse_adc_eth) begin
|
||||
cfg_wait_adc_ack <= 1'b0;
|
||||
end
|
||||
|
||||
case (eth_state)
|
||||
ST_IDLE: begin
|
||||
cfg_byte_cnt <= '0;
|
||||
cfg_shift_eth <= cfg_shift_eth;
|
||||
|
||||
if (axis_hs) begin
|
||||
// if busy, drop the whole packet
|
||||
if (busy_flag_eth) begin
|
||||
if (!s_axis_tlast) begin
|
||||
eth_state <= ST_DISCARD;
|
||||
end
|
||||
end else begin
|
||||
unique case (s_axis_tdata)
|
||||
CMD_SOFT_RESET: begin
|
||||
rst_toggle_eth <= ~rst_toggle_eth;
|
||||
end
|
||||
|
||||
CMD_START: begin
|
||||
start_toggle_eth <= ~start_toggle_eth;
|
||||
busy_flag_eth <= 1'b1;
|
||||
end
|
||||
|
||||
CMD_SET_DATA: begin
|
||||
// expect exactly 12 bytes after command
|
||||
if (s_axis_tlast) begin
|
||||
// no payload, invalid packet
|
||||
eth_state <= ST_IDLE;
|
||||
end else begin
|
||||
cfg_byte_cnt <= 4'd0;
|
||||
cfg_shift_eth <= '0;
|
||||
eth_state <= ST_RECV_CFG;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
// unknown command: discard packet remainder if any
|
||||
if (!s_axis_tlast) begin
|
||||
eth_state <= ST_DISCARD;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_RECV_CFG: begin
|
||||
if (axis_hs) begin
|
||||
// little endian packing
|
||||
cfg_shift_eth[cfg_byte_cnt*8 +: 8] <= s_axis_tdata;
|
||||
|
||||
if (cfg_byte_cnt == 4'd11) begin
|
||||
// this must be the final payload byte
|
||||
if (s_axis_tlast) begin
|
||||
cfg_bus_eth <= {s_axis_tdata, cfg_shift_eth[87:0]};
|
||||
cfg_req_toggle_dac_eth <= ~cfg_req_toggle_dac_eth;
|
||||
cfg_req_toggle_adc_eth <= ~cfg_req_toggle_adc_eth;
|
||||
cfg_wait_dac_ack <= 1'b1;
|
||||
cfg_wait_adc_ack <= 1'b1;
|
||||
eth_state <= ST_WAIT_CFG_ACK;
|
||||
end else begin
|
||||
// too many bytes in packet
|
||||
eth_state <= ST_DISCARD;
|
||||
end
|
||||
end else begin
|
||||
// early tlast means packet too short!!
|
||||
if (s_axis_tlast) begin
|
||||
eth_state <= ST_IDLE;
|
||||
end else begin
|
||||
cfg_byte_cnt <= cfg_byte_cnt + 4'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT_CFG_ACK: begin
|
||||
// any incoming packet while waiting ack is discarded
|
||||
if (cfg_ack_pulse_dac_eth || cfg_ack_pulse_adc_eth) begin
|
||||
if ((~cfg_wait_dac_ack || cfg_ack_pulse_dac_eth) &&
|
||||
(~cfg_wait_adc_ack || cfg_ack_pulse_adc_eth)) begin
|
||||
eth_state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
if (axis_hs && !s_axis_tlast) begin
|
||||
eth_state <= ST_DISCARD;
|
||||
end
|
||||
end
|
||||
|
||||
ST_DISCARD: begin
|
||||
if (axis_hs && s_axis_tlast) begin
|
||||
eth_state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
eth_state <= ST_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ETH -> DAC: start/reset event sync
|
||||
(* ASYNC_REG = "TRUE" *) logic start_meta_dac, start_sync_dac;
|
||||
logic start_sync_dac_d;
|
||||
(* ASYNC_REG = "TRUE" *) logic rst_meta_dac, rst_sync_dac;
|
||||
logic rst_sync_dac_d;
|
||||
|
||||
wire dac_start_pulse = start_sync_dac ^ start_sync_dac_d;
|
||||
wire dac_rst_pulse = rst_sync_dac ^ rst_sync_dac_d;
|
||||
|
||||
always_ff @(posedge dac_clk_in or posedge dac_rst_int) begin
|
||||
if (dac_rst_int) begin
|
||||
start_meta_dac <= 1'b0;
|
||||
start_sync_dac <= 1'b0;
|
||||
start_sync_dac_d <= 1'b0;
|
||||
|
||||
rst_meta_dac <= 1'b0;
|
||||
rst_sync_dac <= 1'b0;
|
||||
rst_sync_dac_d <= 1'b0;
|
||||
|
||||
dac_start <= 1'b0;
|
||||
dac_rst <= 1'b0;
|
||||
end else begin
|
||||
start_meta_dac <= start_toggle_eth;
|
||||
start_sync_dac <= start_meta_dac;
|
||||
start_sync_dac_d <= start_sync_dac;
|
||||
|
||||
rst_meta_dac <= rst_toggle_eth;
|
||||
rst_sync_dac <= rst_meta_dac;
|
||||
rst_sync_dac_d <= rst_sync_dac;
|
||||
|
||||
dac_start <= dac_start_pulse;
|
||||
dac_rst <= dac_rst_pulse;
|
||||
end
|
||||
end
|
||||
|
||||
// ETH -> ADC: start/reset event sync
|
||||
(* ASYNC_REG = "TRUE" *) logic start_meta_adc, start_sync_adc;
|
||||
logic start_sync_adc_d;
|
||||
(* ASYNC_REG = "TRUE" *) logic rst_meta_adc, rst_sync_adc;
|
||||
logic rst_sync_adc_d;
|
||||
|
||||
wire adc_start_pulse = start_sync_adc ^ start_sync_adc_d;
|
||||
wire adc_rst_pulse = rst_sync_adc ^ rst_sync_adc_d;
|
||||
|
||||
always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin
|
||||
if (adc_rst_int) begin
|
||||
start_meta_adc <= 1'b0;
|
||||
start_sync_adc <= 1'b0;
|
||||
start_sync_adc_d <= 1'b0;
|
||||
|
||||
rst_meta_adc <= 1'b0;
|
||||
rst_sync_adc <= 1'b0;
|
||||
rst_sync_adc_d <= 1'b0;
|
||||
|
||||
adc_start <= 1'b0;
|
||||
adc_rst <= 1'b0;
|
||||
end else begin
|
||||
start_meta_adc <= start_toggle_eth;
|
||||
start_sync_adc <= start_meta_adc;
|
||||
start_sync_adc_d <= start_sync_adc;
|
||||
|
||||
rst_meta_adc <= rst_toggle_eth;
|
||||
rst_sync_adc <= rst_meta_adc;
|
||||
rst_sync_adc_d <= rst_sync_adc;
|
||||
|
||||
adc_start <= adc_start_pulse;
|
||||
adc_rst <= adc_rst_pulse;
|
||||
end
|
||||
end
|
||||
|
||||
// ETH -> DAC config CDC
|
||||
// cfg_bus_eth is kept stable in ETH domain until DAC and ADC both ACK.
|
||||
(* ASYNC_REG = "TRUE" *) logic cfg_req_meta_dac, cfg_req_sync_dac;
|
||||
logic cfg_req_sync_dac_d;
|
||||
wire cfg_req_pulse_dac = cfg_req_sync_dac ^ cfg_req_sync_dac_d;
|
||||
|
||||
always_ff @(posedge dac_clk_in or posedge dac_rst_int) begin
|
||||
if (dac_rst_int) begin
|
||||
cfg_req_meta_dac <= 1'b0;
|
||||
cfg_req_sync_dac <= 1'b0;
|
||||
cfg_req_sync_dac_d<= 1'b0;
|
||||
cfg_ack_toggle_dac<= 1'b0;
|
||||
|
||||
dac_pulse_width <= '0;
|
||||
dac_pulse_period <= '0;
|
||||
dac_pulse_num <= '0;
|
||||
dac_pulse_height <= '0;
|
||||
end else begin
|
||||
cfg_req_meta_dac <= cfg_req_toggle_dac_eth;
|
||||
cfg_req_sync_dac <= cfg_req_meta_dac;
|
||||
cfg_req_sync_dac_d <= cfg_req_sync_dac;
|
||||
|
||||
if (cfg_req_pulse_dac) begin
|
||||
dac_pulse_width <= cfg_bus_eth[31:0];
|
||||
dac_pulse_period <= cfg_bus_eth[63:32];
|
||||
dac_pulse_num <= cfg_bus_eth[79:64];
|
||||
dac_pulse_height <= cfg_bus_eth[80 +: DAC_DATA_WIDTH];
|
||||
|
||||
cfg_ack_toggle_dac <= ~cfg_ack_toggle_dac;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ETH -> ADC config CDC
|
||||
logic cfg_req_meta_adc, cfg_req_sync_adc, cfg_req_sync_adc_d;
|
||||
wire cfg_req_pulse_adc = cfg_req_sync_adc ^ cfg_req_sync_adc_d;
|
||||
|
||||
always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin
|
||||
if (adc_rst_int) begin
|
||||
cfg_req_meta_adc <= 1'b0;
|
||||
cfg_req_sync_adc <= 1'b0;
|
||||
cfg_req_sync_adc_d <= 1'b0;
|
||||
cfg_ack_toggle_adc <= 1'b0;
|
||||
|
||||
adc_pulse_period <= '0;
|
||||
adc_pulse_num <= '0;
|
||||
end else begin
|
||||
cfg_req_meta_adc <= cfg_req_toggle_adc_eth;
|
||||
cfg_req_sync_adc <= cfg_req_meta_adc;
|
||||
cfg_req_sync_adc_d <= cfg_req_sync_adc;
|
||||
|
||||
if (cfg_req_pulse_adc) begin
|
||||
adc_pulse_period <= cfg_bus_eth[63:32];
|
||||
adc_pulse_num <= cfg_bus_eth[79:64];
|
||||
|
||||
cfg_ack_toggle_adc <= ~cfg_ack_toggle_adc;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
52
rtl/controller/tests/Makefile
Normal file
52
rtl/controller/tests/Makefile
Normal file
@ -0,0 +1,52 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# Copyright (c) 2025 FPGA Ninja, LLC
|
||||
#
|
||||
# Authors:
|
||||
# - Alex Forencich
|
||||
#
|
||||
|
||||
# FPGA settings
|
||||
FPGA_PART = xc7a35tfgg484-1
|
||||
FPGA_TOP = control
|
||||
FPGA_ARCH = artix7
|
||||
|
||||
RTL_DIR = ../src
|
||||
|
||||
|
||||
include ../../../scripts/vivado.mk
|
||||
|
||||
SYN_FILES += $(sort $(shell find ../src -type f \( -name '*.v' -o -name '*.sv' \)))
|
||||
|
||||
XCI_FILES = $(sort $(shell find ../src -type f -name '*.xci'))
|
||||
|
||||
XDC_FILES += ../../../constraints/ax7a035b.xdc
|
||||
XDC_FILES += test_timing.xdc
|
||||
|
||||
SYN_FILES += controller_tb.sv
|
||||
SIM_TOP = control_tb
|
||||
|
||||
|
||||
program: $(PROJECT).bit
|
||||
echo "open_hw_manager" > program.tcl
|
||||
echo "connect_hw_server" >> program.tcl
|
||||
echo "open_hw_target" >> program.tcl
|
||||
echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl
|
||||
echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl
|
||||
echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl
|
||||
echo "program_hw_devices [current_hw_device]" >> program.tcl
|
||||
echo "exit" >> program.tcl
|
||||
vivado -nojournal -nolog -mode batch -source program.tcl
|
||||
|
||||
$(PROJECT).mcs $(PROJECT).prm: $(PROJECT).bit
|
||||
echo "write_cfgmem -force -format mcs -size 16 -interface SPIx4 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl
|
||||
echo "exit" >> generate_mcs.tcl
|
||||
vivado -nojournal -nolog -mode batch -source generate_mcs.tcl
|
||||
mkdir -p rev
|
||||
COUNT=100; \
|
||||
while [ -e rev/$*_rev$$COUNT.bit ]; \
|
||||
do COUNT=$$((COUNT+1)); done; \
|
||||
COUNT=$$((COUNT-1)); \
|
||||
for x in .mcs .prm; \
|
||||
do cp $*$$x rev/$*_rev$$COUNT$$x; \
|
||||
echo "Output: rev/$*_rev$$COUNT$$x"; done;
|
||||
377
rtl/controller/tests/controller_tb.sv
Normal file
377
rtl/controller/tests/controller_tb.sv
Normal file
@ -0,0 +1,377 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_control;
|
||||
|
||||
localparam int unsigned DAC_DATA_WIDTH = 12;
|
||||
|
||||
|
||||
// Clocks / reset
|
||||
logic eth_clk_in;
|
||||
logic dac_clk_in;
|
||||
logic adc_clk_in;
|
||||
logic rst_n;
|
||||
|
||||
// axi stream (input)
|
||||
logic [7:0] s_axis_tdata;
|
||||
logic s_axis_tvalid;
|
||||
logic s_axis_tready;
|
||||
logic s_axis_tlast;
|
||||
|
||||
// ADC side input
|
||||
logic finish;
|
||||
|
||||
// DUT outputs
|
||||
logic [31:0] dac_pulse_width;
|
||||
logic [31:0] dac_pulse_period;
|
||||
logic [DAC_DATA_WIDTH-1:0] dac_pulse_height;
|
||||
logic [15:0] dac_pulse_num;
|
||||
|
||||
logic [31:0] adc_pulse_period;
|
||||
logic [15:0] adc_pulse_num;
|
||||
|
||||
logic dac_start;
|
||||
logic adc_start;
|
||||
logic dac_rst;
|
||||
logic adc_rst;
|
||||
|
||||
|
||||
// DUT
|
||||
control #(
|
||||
.DAC_DATA_WIDTH(DAC_DATA_WIDTH)
|
||||
) dut (
|
||||
.eth_clk_in (eth_clk_in),
|
||||
.dac_clk_in (dac_clk_in),
|
||||
.adc_clk_in (adc_clk_in),
|
||||
.rst_n (rst_n),
|
||||
|
||||
.s_axis_tdata (s_axis_tdata),
|
||||
.s_axis_tvalid (s_axis_tvalid),
|
||||
.s_axis_tready (s_axis_tready),
|
||||
.s_axis_tlast (s_axis_tlast),
|
||||
|
||||
.finish (finish),
|
||||
|
||||
.dac_pulse_width (dac_pulse_width),
|
||||
.dac_pulse_period (dac_pulse_period),
|
||||
.dac_pulse_height (dac_pulse_height),
|
||||
.dac_pulse_num (dac_pulse_num),
|
||||
|
||||
.adc_pulse_period (adc_pulse_period),
|
||||
.adc_pulse_num (adc_pulse_num),
|
||||
|
||||
.dac_start (dac_start),
|
||||
.adc_start (adc_start),
|
||||
.dac_rst (dac_rst),
|
||||
.adc_rst (adc_rst)
|
||||
);
|
||||
|
||||
|
||||
// Clock generation
|
||||
initial begin
|
||||
eth_clk_in = 1'b0;
|
||||
forever #(1 * 4.000) eth_clk_in = ~eth_clk_in; // 125 MHz
|
||||
end
|
||||
|
||||
initial begin
|
||||
dac_clk_in = 1'b0;
|
||||
forever #(1 * 3.846153846) dac_clk_in = ~dac_clk_in; // ~130 MHz
|
||||
end
|
||||
|
||||
initial begin
|
||||
adc_clk_in = 1'b0;
|
||||
forever #(1 * 7.692307692) adc_clk_in = ~adc_clk_in; // ~65 MHz
|
||||
end
|
||||
|
||||
|
||||
// pulse counters and monitors for testing
|
||||
int dac_rst_count;
|
||||
int adc_rst_count;
|
||||
int dac_start_count;
|
||||
int adc_start_count;
|
||||
|
||||
always_ff @(posedge dac_clk_in) begin
|
||||
if (!rst_n) begin
|
||||
dac_rst_count <= 0;
|
||||
dac_start_count <= 0;
|
||||
end else begin
|
||||
if (dac_rst) dac_rst_count <= dac_rst_count + 1;
|
||||
if (dac_start) dac_start_count <= dac_start_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge adc_clk_in) begin
|
||||
if (!rst_n) begin
|
||||
adc_rst_count <= 0;
|
||||
adc_start_count <= 0;
|
||||
end else begin
|
||||
if (adc_rst) adc_rst_count <= adc_rst_count + 1;
|
||||
if (adc_start) adc_start_count <= adc_start_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
// some helpers for axi
|
||||
task automatic axis_send_byte(input logic [7:0] data, input logic last);
|
||||
begin
|
||||
@(negedge eth_clk_in);
|
||||
s_axis_tdata <= data;
|
||||
s_axis_tvalid <= 1'b1;
|
||||
s_axis_tlast <= last;
|
||||
|
||||
@(posedge eth_clk_in);
|
||||
while (!s_axis_tready) begin
|
||||
@(posedge eth_clk_in);
|
||||
end
|
||||
|
||||
s_axis_tvalid <= 1'b0;
|
||||
s_axis_tlast <= 1'b0;
|
||||
s_axis_tdata <= '0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic send_cmd(input logic [7:0] cmd);
|
||||
begin
|
||||
axis_send_byte(cmd, 1'b1);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic send_set_data(
|
||||
input logic [31:0] pulse_width,
|
||||
input logic [31:0] pulse_period,
|
||||
input logic [15:0] pulse_num,
|
||||
input logic [15:0] pulse_height_raw
|
||||
);
|
||||
logic [95:0] payload;
|
||||
int i;
|
||||
begin
|
||||
// little-endian payload layout:
|
||||
// [31:0] pulse_width
|
||||
// [63:32] pulse_period
|
||||
// [79:64] pulse_num
|
||||
// [95:80] pulse_height_raw
|
||||
payload = {pulse_height_raw, pulse_num, pulse_period, pulse_width};
|
||||
|
||||
axis_send_byte(8'h88, 1'b0); // CMD_SET_DATA
|
||||
|
||||
for (i = 0; i < 12; i++) begin
|
||||
axis_send_byte(payload[i*8 +: 8], (i == 11));
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic pulse_finish;
|
||||
begin
|
||||
@(posedge adc_clk_in);
|
||||
finish <= 1'b1;
|
||||
@(posedge adc_clk_in);
|
||||
finish <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
|
||||
// waiters
|
||||
task automatic wait_dac_rst_count(input int expected, input int max_cycles = 100);
|
||||
int i;
|
||||
begin
|
||||
for (i = 0; i < max_cycles; i++) begin
|
||||
@(posedge dac_clk_in);
|
||||
if (dac_rst_count >= expected) return;
|
||||
end
|
||||
$fatal(1, "Timeout waiting for dac_rst_count >= %0d, current=%0d", expected, dac_rst_count);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic wait_adc_rst_count(input int expected, input int max_cycles = 100);
|
||||
int i;
|
||||
begin
|
||||
for (i = 0; i < max_cycles; i++) begin
|
||||
@(posedge adc_clk_in);
|
||||
if (adc_rst_count >= expected) return;
|
||||
end
|
||||
$fatal(1, "Timeout waiting for adc_rst_count >= %0d, current=%0d", expected, adc_rst_count);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic wait_dac_start_count(input int expected, input int max_cycles = 100);
|
||||
int i;
|
||||
begin
|
||||
for (i = 0; i < max_cycles; i++) begin
|
||||
@(posedge dac_clk_in);
|
||||
if (dac_start_count >= expected) return;
|
||||
end
|
||||
$fatal(1, "Timeout waiting for dac_start_count >= %0d, current=%0d", expected, dac_start_count);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic wait_adc_start_count(input int expected, input int max_cycles = 100);
|
||||
int i;
|
||||
begin
|
||||
for (i = 0; i < max_cycles; i++) begin
|
||||
@(posedge adc_clk_in);
|
||||
if (adc_start_count >= expected) return;
|
||||
end
|
||||
$fatal(1, "Timeout waiting for adc_start_count >= %0d, current=%0d", expected, adc_start_count);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic wait_cfg_applied(
|
||||
input logic [31:0] exp_pulse_width,
|
||||
input logic [31:0] exp_pulse_period,
|
||||
input logic [15:0] exp_pulse_num,
|
||||
input logic [15:0] exp_pulse_height_raw,
|
||||
input int max_cycles = 200
|
||||
);
|
||||
logic [DAC_DATA_WIDTH-1:0] exp_dac_height;
|
||||
int i;
|
||||
begin
|
||||
exp_dac_height = exp_pulse_height_raw[DAC_DATA_WIDTH-1:0];
|
||||
|
||||
for (i = 0; i < max_cycles; i++) begin
|
||||
@(posedge eth_clk_in);
|
||||
if ((dac_pulse_width === exp_pulse_width ) &&
|
||||
(dac_pulse_period === exp_pulse_period) &&
|
||||
(dac_pulse_num === exp_pulse_num ) &&
|
||||
(dac_pulse_height === exp_dac_height ) &&
|
||||
(adc_pulse_period === exp_pulse_period) &&
|
||||
(adc_pulse_num === exp_pulse_num )) begin
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
$fatal(1,
|
||||
"Timeout waiting config outputs. Got: dac_width=%h dac_period=%h dac_num=%h dac_height=%h adc_period=%h adc_num=%h",
|
||||
dac_pulse_width, dac_pulse_period, dac_pulse_num, dac_pulse_height,
|
||||
adc_pulse_period, adc_pulse_num
|
||||
);
|
||||
end
|
||||
endtask
|
||||
|
||||
|
||||
// Test sequence
|
||||
logic [31:0] test_pulse_width;
|
||||
logic [31:0] test_pulse_period;
|
||||
logic [15:0] test_pulse_num;
|
||||
logic [15:0] test_pulse_height_raw;
|
||||
|
||||
initial begin
|
||||
// defaults
|
||||
rst_n = 1'b0;
|
||||
s_axis_tdata = '0;
|
||||
s_axis_tvalid = 1'b0;
|
||||
s_axis_tlast = 1'b0;
|
||||
finish = 1'b0;
|
||||
|
||||
test_pulse_width = 32'h11223344;
|
||||
test_pulse_period = 32'h55667788;
|
||||
test_pulse_num = 16'hA1B2;
|
||||
test_pulse_height_raw = 16'h0CDE; // for DAC_DATA_WIDTH=12 => 12'hCDE
|
||||
|
||||
repeat (10) @(posedge eth_clk_in);
|
||||
rst_n = 1'b1;
|
||||
|
||||
repeat (10) @(posedge eth_clk_in);
|
||||
|
||||
$display("[%0t] TEST 1: soft_reset", $time);
|
||||
send_cmd(8'h0F);
|
||||
|
||||
wait_dac_rst_count(1);
|
||||
wait_adc_rst_count(1);
|
||||
|
||||
if (dac_rst_count != 1) begin
|
||||
$fatal(1, "Expected exactly one dac_rst pulse after first soft_reset, got %0d", dac_rst_count);
|
||||
end
|
||||
if (adc_rst_count != 1) begin
|
||||
$fatal(1, "Expected exactly one adc_rst pulse after first soft_reset, got %0d", adc_rst_count);
|
||||
end
|
||||
|
||||
$display("[%0t] TEST 1 passed", $time);
|
||||
|
||||
$display("[%0t] TEST 2: set_data", $time);
|
||||
send_set_data(
|
||||
test_pulse_width,
|
||||
test_pulse_period,
|
||||
test_pulse_num,
|
||||
test_pulse_height_raw
|
||||
);
|
||||
|
||||
wait_cfg_applied(
|
||||
test_pulse_width,
|
||||
test_pulse_period,
|
||||
test_pulse_num,
|
||||
test_pulse_height_raw
|
||||
);
|
||||
|
||||
if (dac_pulse_width !== 32'h11223344) begin
|
||||
$fatal(1, "dac_pulse_width mismatch: got %h expected %h", dac_pulse_width, 32'h11223344);
|
||||
end
|
||||
if (dac_pulse_period !== 32'h55667788) begin
|
||||
$fatal(1, "dac_pulse_period mismatch: got %h expected %h", dac_pulse_period, 32'h55667788);
|
||||
end
|
||||
if (dac_pulse_num !== 16'hA1B2) begin
|
||||
$fatal(1, "dac_pulse_num mismatch: got %h expected %h", dac_pulse_num, 16'hA1B2);
|
||||
end
|
||||
if (dac_pulse_height !== 12'hCDE) begin
|
||||
$fatal(1, "dac_pulse_height mismatch: got %h expected %h", dac_pulse_height, 12'hCDE);
|
||||
end
|
||||
if (adc_pulse_period !== 32'h55667788) begin
|
||||
$fatal(1, "adc_pulse_period mismatch: got %h expected %h", adc_pulse_period, 32'h55667788);
|
||||
end
|
||||
if (adc_pulse_num !== 16'hA1B2) begin
|
||||
$fatal(1, "adc_pulse_num mismatch: got %h expected %h", adc_pulse_num, 16'hA1B2);
|
||||
end
|
||||
|
||||
$display("[%0t] TEST 2 passed", $time);
|
||||
|
||||
repeat (20) @(posedge eth_clk_in);
|
||||
|
||||
$display("[%0t] TEST 3: start", $time);
|
||||
send_cmd(8'hF0);
|
||||
|
||||
wait_dac_start_count(1);
|
||||
wait_adc_start_count(1);
|
||||
|
||||
if (dac_start_count != 1) begin
|
||||
$fatal(1, "Expected exactly one dac_start pulse after first start, got %0d", dac_start_count);
|
||||
end
|
||||
if (adc_start_count != 1) begin
|
||||
$fatal(1, "Expected exactly one adc_start pulse after first start, got %0d", adc_start_count);
|
||||
end
|
||||
|
||||
$display("[%0t] TEST 3 start pulses passed", $time);
|
||||
|
||||
// release busy by finish pulse from ADC domain
|
||||
$display("[%0t] Sending finish pulse", $time);
|
||||
pulse_finish();
|
||||
|
||||
// a bit of wait for finish CDC back to ETH
|
||||
repeat (20) @(posedge eth_clk_in);
|
||||
|
||||
// sanity check that commands are accepted again after finish
|
||||
$display("[%0t] TEST 4: soft_reset after finish", $time);
|
||||
send_cmd(8'h0F);
|
||||
|
||||
wait_dac_rst_count(2);
|
||||
wait_adc_rst_count(2);
|
||||
|
||||
if (dac_rst_count != 2) begin
|
||||
$fatal(1, "Expected exactly two dac_rst pulses total, got %0d", dac_rst_count);
|
||||
end
|
||||
if (adc_rst_count != 2) begin
|
||||
$fatal(1, "Expected exactly two adc_rst pulses total, got %0d", adc_rst_count);
|
||||
end
|
||||
|
||||
$display("[%0t] TEST 4 passed", $time);
|
||||
|
||||
$display("==============================================");
|
||||
$display("ALL BASIC TESTS PASSED");
|
||||
$display("dac_rst_count = %0d", dac_rst_count);
|
||||
$display("adc_rst_count = %0d", adc_rst_count);
|
||||
$display("dac_start_count = %0d", dac_start_count);
|
||||
$display("adc_start_count = %0d", adc_start_count);
|
||||
$display("==============================================");
|
||||
|
||||
#100;
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
13
rtl/controller/tests/test_timing.xdc
Normal file
13
rtl/controller/tests/test_timing.xdc
Normal file
@ -0,0 +1,13 @@
|
||||
# Primary clocks
|
||||
create_clock -name eth_clk -period 8.000 [get_ports eth_clk_in]
|
||||
create_clock -name dac_clk -period 7.692 [get_ports dac_clk_in]
|
||||
create_clock -name adc_clk -period 15.385 [get_ports adc_clk_in]
|
||||
|
||||
|
||||
# Asynchronous clock groups
|
||||
# eth, dac, adc are independent domains
|
||||
|
||||
set_clock_groups -name ASYNC_ETH_DAC_ADC -asynchronous \
|
||||
-group [get_clocks eth_clk] \
|
||||
-group [get_clocks dac_clk] \
|
||||
-group [get_clocks adc_clk]
|
||||
Reference in New Issue
Block a user