make downlink core export as just one verilog file
This commit is contained in:
parent
f5c2af06d0
commit
e48f60e03a
|
|
@ -1,2 +1,4 @@
|
|||
*.log
|
||||
*.bit
|
||||
*.vcd
|
||||
*.out
|
||||
|
|
|
|||
|
|
@ -0,0 +1,612 @@
|
|||
`default_nettype none
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module fifo (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire [WIDTH - 1:0] data_in,
|
||||
input wire input_ready,
|
||||
|
||||
input wire request_output,
|
||||
output logic [WIDTH - 1:0] data_out,
|
||||
output logic output_valid,
|
||||
|
||||
output logic [AW:0] size,
|
||||
output logic empty,
|
||||
output logic full
|
||||
);
|
||||
|
||||
parameter WIDTH = 8;
|
||||
parameter DEPTH = 4096;
|
||||
localparam AW = $clog2(DEPTH);
|
||||
|
||||
logic [AW:0] write_pointer;
|
||||
logic [AW:0] read_pointer;
|
||||
|
||||
logic empty_int;
|
||||
assign empty_int = (write_pointer[AW] == read_pointer[AW]);
|
||||
|
||||
logic full_or_empty;
|
||||
assign full_or_empty = (write_pointer[AW-1:0] == read_pointer[AW-1:0]);
|
||||
|
||||
assign full = full_or_empty & !empty_int;
|
||||
assign empty = full_or_empty & empty_int;
|
||||
assign size = write_pointer - read_pointer;
|
||||
|
||||
logic output_valid_pip_0;
|
||||
logic output_valid_pip_1;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (input_ready && ~full)
|
||||
write_pointer <= write_pointer + 1'd1;
|
||||
|
||||
if (request_output && ~empty)
|
||||
read_pointer <= read_pointer + 1'd1;
|
||||
output_valid_pip_0 <= request_output;
|
||||
output_valid_pip_1 <= output_valid_pip_0;
|
||||
output_valid <= output_valid_pip_1;
|
||||
|
||||
if (rst) begin
|
||||
read_pointer <= 0;
|
||||
write_pointer <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
xilinx_true_dual_port_read_first_2_clock_ram #(
|
||||
.RAM_WIDTH(WIDTH),
|
||||
.RAM_DEPTH(DEPTH),
|
||||
.RAM_PERFORMANCE("HIGH_PERFORMANCE")
|
||||
|
||||
) buffer (
|
||||
|
||||
// write port
|
||||
.clka(clk),
|
||||
.rsta(rst),
|
||||
.ena(1),
|
||||
.addra(write_pointer),
|
||||
.dina(data_in),
|
||||
.wea(input_ready),
|
||||
.regcea(1),
|
||||
.douta(),
|
||||
|
||||
// read port
|
||||
.clkb(clk),
|
||||
.rstb(rst),
|
||||
.enb(1),
|
||||
.addrb(read_pointer),
|
||||
.dinb(),
|
||||
.web(0),
|
||||
.regceb(1),
|
||||
.doutb(data_out));
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
`default_nettype none
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/*
|
||||
This ILA was autogenerated on 05/02/2023 10:10:20 by fischerm
|
||||
|
||||
If this breaks or if you've got dank formal verification memes,
|
||||
please contact fischerm [at] mit.edu.
|
||||
*/
|
||||
|
||||
`define IDLE 0
|
||||
`define ARM 1
|
||||
`define FILL 2
|
||||
`define DOWNLINK 3
|
||||
|
||||
`define ARM_BYTE 8'b00110000
|
||||
|
||||
module ila (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/* Begin autogenerated probe definitions */
|
||||
input wire larry,
|
||||
input wire curly,
|
||||
input wire moe,
|
||||
input wire [2:0] shemp,
|
||||
/* End autogenerated probe definitions */
|
||||
|
||||
input wire rxd,
|
||||
output logic txd);
|
||||
|
||||
/* Begin autogenerated parameters */
|
||||
localparam SAMPLE_WIDTH = 6;
|
||||
localparam SAMPLE_DEPTH = 4096;
|
||||
|
||||
localparam DATA_WIDTH = 8;
|
||||
localparam BAUDRATE = 115200;
|
||||
localparam CLK_FREQ_HZ = 100000000;
|
||||
|
||||
logic trigger;
|
||||
assign trigger = (larry && curly && ~moe);
|
||||
|
||||
logic [SAMPLE_WIDTH - 1 : 0] concat;
|
||||
assign concat = {larry, curly, moe, shemp};;
|
||||
/* End autogenerated parameters */
|
||||
|
||||
|
||||
// FIFO
|
||||
logic [7:0] fifo_data_in;
|
||||
logic fifo_input_ready;
|
||||
|
||||
logic fifo_request_output;
|
||||
logic [7:0] fifo_data_out;
|
||||
logic fifo_output_valid;
|
||||
|
||||
logic [11:0] fifo_size;
|
||||
logic fifo_empty;
|
||||
logic fifo_full;
|
||||
|
||||
fifo #(
|
||||
.WIDTH(SAMPLE_WIDTH),
|
||||
.DEPTH(SAMPLE_DEPTH)
|
||||
) fifo (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
|
||||
.data_in(fifo_data_in),
|
||||
.input_ready(fifo_input_ready),
|
||||
|
||||
.request_output(fifo_request_output),
|
||||
.data_out(fifo_data_out),
|
||||
.output_valid(fifo_output_valid),
|
||||
|
||||
.size(fifo_size),
|
||||
.empty(fifo_empty),
|
||||
.full(fifo_full));
|
||||
|
||||
// Serial interface
|
||||
logic tx_start;
|
||||
logic [7:0] tx_data;
|
||||
logic tx_busy;
|
||||
|
||||
logic [7:0] rx_data;
|
||||
logic rx_ready;
|
||||
logic rx_busy;
|
||||
|
||||
|
||||
uart_tx #(
|
||||
.DATA_WIDTH(DATA_WIDTH),
|
||||
.CLK_FREQ_HZ(CLK_FREQ_HZ),
|
||||
.BAUDRATE(BAUDRATE))
|
||||
tx (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
.start(tx_start),
|
||||
.data(tx_data),
|
||||
|
||||
.busy(tx_busy),
|
||||
.txd(txd));
|
||||
|
||||
uart_rx #(
|
||||
.DATA_WIDTH(DATA_WIDTH),
|
||||
.CLK_FREQ_HZ(CLK_FREQ_HZ),
|
||||
.BAUDRATE(BAUDRATE))
|
||||
rx (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
.rxd(rxd),
|
||||
|
||||
.data(rx_data),
|
||||
.ready(rx_ready),
|
||||
.busy(rx_busy));
|
||||
|
||||
|
||||
/* State Machine */
|
||||
/*
|
||||
|
||||
IDLE:
|
||||
- literally nothing is happening. the FIFO isn't being written to or read from. it should be empty.
|
||||
- an arm command over serial is what brings us into the ARM state
|
||||
|
||||
ARM:
|
||||
- popping things onto FIFO. if the fifo is halfway full, we pop them off too.
|
||||
- meeting the trigger condition is what moves us into the filing state
|
||||
|
||||
FILL:
|
||||
- popping things onto FIFO, until it's full. once it is full, we move into the downlinking state
|
||||
|
||||
DOWNLINK:
|
||||
- popping thing off of the FIFO until it's empty. once it's empty, we move back into the IDLE state
|
||||
*/
|
||||
|
||||
/* Downlink State Machine Controller */
|
||||
/*
|
||||
|
||||
- ila enters the downlink state
|
||||
- set fifo_output_request high for a clock cycle
|
||||
- when fifo_output_valid goes high, send fifo_data_out across the line
|
||||
- do nothing until tx_busy goes low
|
||||
- goto step 2
|
||||
|
||||
*/
|
||||
|
||||
logic [1:0] state;
|
||||
logic [2:0] downlink_fsm_state;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if(rst) begin
|
||||
state <= `IDLE;
|
||||
downlink_fsm_state <= 0;
|
||||
tx_data <= 0;
|
||||
tx_start <= 0;
|
||||
end
|
||||
else begin
|
||||
|
||||
case (state)
|
||||
`IDLE : begin
|
||||
fifo_input_ready <= 0;
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if (rx_ready && rx_data == `ARM_BYTE) state <= `ARM;
|
||||
|
||||
end
|
||||
|
||||
`ARM : begin
|
||||
// place samples into FIFO
|
||||
fifo_input_ready <= 1;
|
||||
fifo_data_in <= concat;
|
||||
|
||||
// remove old samples if we're more than halfway full
|
||||
fifo_request_output <= (fifo_size >= SAMPLE_DEPTH / 2);
|
||||
|
||||
if(trigger) state <= `FILL;
|
||||
end
|
||||
|
||||
`FILL : begin
|
||||
// place samples into FIFO
|
||||
fifo_input_ready <= 1;
|
||||
fifo_data_in <= concat;
|
||||
|
||||
// don't pop anything out the FIFO
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if(fifo_size == SAMPLE_DEPTH - 1) state <= `DOWNLINK;
|
||||
end
|
||||
|
||||
`DOWNLINK : begin
|
||||
// place no samples into FIFO
|
||||
fifo_input_ready <= 0;
|
||||
|
||||
|
||||
case (downlink_fsm_state)
|
||||
0 : begin
|
||||
if (~fifo_empty) begin
|
||||
fifo_request_output <= 1;
|
||||
downlink_fsm_state <= 1;
|
||||
end
|
||||
|
||||
else state <= `IDLE;
|
||||
end
|
||||
|
||||
1 : begin
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if (fifo_output_valid) begin
|
||||
tx_data <= fifo_data_out;
|
||||
tx_start <= 1;
|
||||
downlink_fsm_state <= 2;
|
||||
end
|
||||
end
|
||||
|
||||
2 : begin
|
||||
tx_start <= 0;
|
||||
|
||||
if (~tx_busy && ~tx_start) downlink_fsm_state <= 0;
|
||||
end
|
||||
endcase
|
||||
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire`default_nettype none
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
|
||||
module uart_tx(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
input wire [DATA_WIDTH-1:0] data,
|
||||
input wire start,
|
||||
|
||||
output logic busy,
|
||||
output logic txd
|
||||
);
|
||||
|
||||
// Just going to stick to 8N1 for now, we'll come back and
|
||||
// parameterize this later.
|
||||
|
||||
parameter DATA_WIDTH = 8;
|
||||
parameter CLK_FREQ_HZ = 100_000_000;
|
||||
parameter BAUDRATE = 115200;
|
||||
|
||||
localparam PRESCALER = CLK_FREQ_HZ / BAUDRATE;
|
||||
|
||||
logic [$clog2(PRESCALER) - 1:0] baud_counter;
|
||||
logic [$clog2(DATA_WIDTH + 2):0] bit_index;
|
||||
logic [DATA_WIDTH - 1:0] data_buf;
|
||||
|
||||
// make secondary logic for baudrate
|
||||
always_ff @(posedge clk) begin
|
||||
if(rst) baud_counter <= 0;
|
||||
else begin
|
||||
baud_counter <= (baud_counter == PRESCALER - 1) ? 0 : baud_counter + 1;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
|
||||
// reset logic
|
||||
if(rst) begin
|
||||
bit_index <= 0;
|
||||
busy <= 0;
|
||||
txd <= 1; // idle high
|
||||
end
|
||||
|
||||
// enter transmitting state logic
|
||||
// don't allow new requests to interrupt current
|
||||
// transfers
|
||||
if(start && ~busy) begin
|
||||
busy <= 1;
|
||||
data_buf <= data;
|
||||
end
|
||||
|
||||
|
||||
// transmitting state logic
|
||||
else if(baud_counter == 0 && busy) begin
|
||||
|
||||
if (bit_index == 0) begin
|
||||
txd <= 0;
|
||||
bit_index <= bit_index + 1;
|
||||
end
|
||||
|
||||
else if ((bit_index < DATA_WIDTH + 1) && (bit_index > 0)) begin
|
||||
txd <= data_buf[bit_index - 1];
|
||||
bit_index <= bit_index + 1;
|
||||
end
|
||||
|
||||
else if (bit_index == DATA_WIDTH + 1) begin
|
||||
txd <= 1;
|
||||
bit_index <= bit_index + 1;
|
||||
end
|
||||
|
||||
else if (bit_index >= DATA_WIDTH + 1) begin
|
||||
busy <= 0;
|
||||
bit_index <= 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
|
||||
// Xilinx True Dual Port RAM, Read First, Dual Clock
|
||||
// This code implements a parameterizable true dual port memory (both ports can read and write).
|
||||
// The behavior of this RAM is when data is written, the prior memory contents at the write
|
||||
// address are presented on the output port. If the output data is
|
||||
// not needed during writes or the last read value is desired to be retained,
|
||||
// it is suggested to use a no change RAM as it is more power efficient.
|
||||
// If a reset or enable is not necessary, it may be tied off or removed from the code.
|
||||
|
||||
module xilinx_true_dual_port_read_first_2_clock_ram #(
|
||||
parameter RAM_WIDTH = 18, // Specify RAM data width
|
||||
parameter RAM_DEPTH = 1024, // Specify RAM depth (number of entries)
|
||||
parameter RAM_PERFORMANCE = "HIGH_PERFORMANCE", // Select "HIGH_PERFORMANCE" or "LOW_LATENCY"
|
||||
parameter INIT_FILE = "" // Specify name/location of RAM initialization file if using one (leave blank if not)
|
||||
) (
|
||||
input [clogb2(RAM_DEPTH-1)-1:0] addra, // Port A address bus, width determined from RAM_DEPTH
|
||||
input [clogb2(RAM_DEPTH-1)-1:0] addrb, // Port B address bus, width determined from RAM_DEPTH
|
||||
input [RAM_WIDTH-1:0] dina, // Port A RAM input data
|
||||
input [RAM_WIDTH-1:0] dinb, // Port B RAM input data
|
||||
input clka, // Port A clock
|
||||
input clkb, // Port B clock
|
||||
input wea, // Port A write enable
|
||||
input web, // Port B write enable
|
||||
input ena, // Port A RAM Enable, for additional power savings, disable port when not in use
|
||||
input enb, // Port B RAM Enable, for additional power savings, disable port when not in use
|
||||
input rsta, // Port A output reset (does not affect memory contents)
|
||||
input rstb, // Port B output reset (does not affect memory contents)
|
||||
input regcea, // Port A output register enable
|
||||
input regceb, // Port B output register enable
|
||||
output [RAM_WIDTH-1:0] douta, // Port A RAM output data
|
||||
output [RAM_WIDTH-1:0] doutb // Port B RAM output data
|
||||
);
|
||||
|
||||
reg [RAM_WIDTH-1:0] BRAM [RAM_DEPTH-1:0];
|
||||
reg [RAM_WIDTH-1:0] ram_data_a = {RAM_WIDTH{1'b0}};
|
||||
reg [RAM_WIDTH-1:0] ram_data_b = {RAM_WIDTH{1'b0}};
|
||||
|
||||
//this loop below allows for rendering with iverilog simulations!
|
||||
/*
|
||||
integer idx;
|
||||
for(idx = 0; idx < RAM_DEPTH; idx = idx+1) begin: cats
|
||||
wire [RAM_WIDTH-1:0] tmp;
|
||||
assign tmp = BRAM[idx];
|
||||
end
|
||||
*/
|
||||
|
||||
// The following code either initializes the memory values to a specified file or to all zeros to match hardware
|
||||
generate
|
||||
if (INIT_FILE != "") begin: use_init_file
|
||||
initial
|
||||
$readmemh(INIT_FILE, BRAM, 0, RAM_DEPTH-1);
|
||||
end else begin: init_bram_to_zero
|
||||
integer ram_index;
|
||||
initial
|
||||
for (ram_index = 0; ram_index < RAM_DEPTH; ram_index = ram_index + 1)
|
||||
BRAM[ram_index] = {RAM_WIDTH{1'b0}};
|
||||
end
|
||||
endgenerate
|
||||
integer idx;
|
||||
// initial begin
|
||||
// for (idx = 0; idx < RAM_DEPTH; idx = idx + 1) begin
|
||||
// $dumpvars(0, BRAM[idx]);
|
||||
// end
|
||||
// end
|
||||
always @(posedge clka)
|
||||
if (ena) begin
|
||||
if (wea)
|
||||
BRAM[addra] <= dina;
|
||||
ram_data_a <= BRAM[addra];
|
||||
end
|
||||
|
||||
always @(posedge clkb)
|
||||
if (enb) begin
|
||||
if (web)
|
||||
BRAM[addrb] <= dinb;
|
||||
ram_data_b <= BRAM[addrb];
|
||||
end
|
||||
|
||||
// The following code generates HIGH_PERFORMANCE (use output register) or LOW_LATENCY (no output register)
|
||||
generate
|
||||
if (RAM_PERFORMANCE == "LOW_LATENCY") begin: no_output_register
|
||||
|
||||
// The following is a 1 clock cycle read latency at the cost of a longer clock-to-out timing
|
||||
assign douta = ram_data_a;
|
||||
assign doutb = ram_data_b;
|
||||
|
||||
end else begin: output_register
|
||||
|
||||
// The following is a 2 clock cycle read latency with improve clock-to-out timing
|
||||
|
||||
reg [RAM_WIDTH-1:0] douta_reg = {RAM_WIDTH{1'b0}};
|
||||
reg [RAM_WIDTH-1:0] doutb_reg = {RAM_WIDTH{1'b0}};
|
||||
|
||||
always @(posedge clka)
|
||||
if (rsta)
|
||||
douta_reg <= {RAM_WIDTH{1'b0}};
|
||||
else if (regcea)
|
||||
douta_reg <= ram_data_a;
|
||||
|
||||
always @(posedge clkb)
|
||||
if (rstb)
|
||||
doutb_reg <= {RAM_WIDTH{1'b0}};
|
||||
else if (regceb)
|
||||
doutb_reg <= ram_data_b;
|
||||
|
||||
assign douta = douta_reg;
|
||||
assign doutb = doutb_reg;
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
// The following function calculates the address width based on specified RAM depth
|
||||
function integer clogb2;
|
||||
input integer depth;
|
||||
for (clogb2=0; depth>0; clogb2=clogb2+1)
|
||||
depth = depth >> 1;
|
||||
endfunction
|
||||
|
||||
endmodule
|
||||
|
||||
// The following is an instantiation template for xilinx_true_dual_port_read_first_2_clock_ram
|
||||
/*
|
||||
// Xilinx True Dual Port RAM, Read First, Dual Clock
|
||||
xilinx_true_dual_port_read_first_2_clock_ram #(
|
||||
.RAM_WIDTH(18), // Specify RAM data width
|
||||
.RAM_DEPTH(1024), // Specify RAM depth (number of entries)
|
||||
.RAM_PERFORMANCE("HIGH_PERFORMANCE"), // Select "HIGH_PERFORMANCE" or "LOW_LATENCY"
|
||||
.INIT_FILE("") // Specify name/location of RAM initialization file if using one (leave blank if not)
|
||||
) your_instance_name (
|
||||
.addra(addra), // Port A address bus, width determined from RAM_DEPTH
|
||||
.addrb(addrb), // Port B address bus, width determined from RAM_DEPTH
|
||||
.dina(dina), // Port A RAM input data, width determined from RAM_WIDTH
|
||||
.dinb(dinb), // Port B RAM input data, width determined from RAM_WIDTH
|
||||
.clka(clka), // Port A clock
|
||||
.clkb(clkb), // Port B clock
|
||||
.wea(wea), // Port A write enable
|
||||
.web(web), // Port B write enable
|
||||
.ena(ena), // Port A RAM Enable, for additional power savings, disable port when not in use
|
||||
.enb(enb), // Port B RAM Enable, for additional power savings, disable port when not in use
|
||||
.rsta(rsta), // Port A output reset (does not affect memory contents)
|
||||
.rstb(rstb), // Port B output reset (does not affect memory contents)
|
||||
.regcea(regcea), // Port A output register enable
|
||||
.regceb(regceb), // Port B output register enable
|
||||
.douta(douta), // Port A RAM output data, width determined from RAM_WIDTH
|
||||
.doutb(doutb) // Port B RAM output data, width determined from RAM_WIDTH
|
||||
);
|
||||
*/
|
||||
|
||||
|
||||
`default_nettype none
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module uart_rx(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
input wire rxd,
|
||||
|
||||
output logic [DATA_WIDTH - 1:0] data,
|
||||
output logic ready,
|
||||
output logic busy
|
||||
);
|
||||
|
||||
// Just going to stick to 8N1 for now, we'll come back and
|
||||
// parameterize this later.
|
||||
|
||||
parameter DATA_WIDTH = 8;
|
||||
parameter CLK_FREQ_HZ = 100_000_000;
|
||||
parameter BAUDRATE = 115200;
|
||||
|
||||
localparam PRESCALER = CLK_FREQ_HZ / BAUDRATE;
|
||||
|
||||
logic [$clog2(PRESCALER) - 1:0] baud_counter;
|
||||
logic [$clog2(DATA_WIDTH + 2):0] bit_index;
|
||||
logic [DATA_WIDTH + 2 : 0] data_buf;
|
||||
|
||||
logic prev_rxd;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
prev_rxd <= rxd;
|
||||
ready <= 0;
|
||||
baud_counter <= (baud_counter == PRESCALER - 1) ? 0 : baud_counter + 1;
|
||||
|
||||
// reset logic
|
||||
if(rst) begin
|
||||
bit_index <= 0;
|
||||
data <= 0;
|
||||
busy <= 0;
|
||||
baud_counter <= 0;
|
||||
end
|
||||
|
||||
// start receiving if we see a falling edge, and not already busy
|
||||
else if (prev_rxd && ~rxd && ~busy) begin
|
||||
busy <= 1;
|
||||
data_buf <= 0;
|
||||
baud_counter <= 0;
|
||||
end
|
||||
|
||||
// if we're actually receiving
|
||||
else if (busy) begin
|
||||
if (baud_counter == PRESCALER / 2) begin
|
||||
data_buf[bit_index] <= rxd;
|
||||
bit_index <= bit_index + 1;
|
||||
|
||||
if (bit_index == DATA_WIDTH + 1) begin
|
||||
busy <= 0;
|
||||
bit_index <= 0;
|
||||
|
||||
|
||||
if (rxd && ~data_buf[0]) begin
|
||||
data <= data_buf[DATA_WIDTH : 1];
|
||||
ready <= 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
`default_nettype none
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/*
|
||||
This ILA was autogenerated on 03/02/2023 23:13:51 by fischerm
|
||||
|
||||
If this breaks or if you've got dank formal verification memes,
|
||||
please contact fischerm [at] mit.edu.
|
||||
*/
|
||||
|
||||
`define IDLE 0
|
||||
`define ARM 1
|
||||
`define FILL 2
|
||||
`define DOWNLINK 3
|
||||
|
||||
`define ARM_BYTE 8'b00110000
|
||||
|
||||
module ila (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
/* Begin autogenerated probe definitions */
|
||||
input wire larry,
|
||||
input wire curly,
|
||||
input wire moe,
|
||||
input wire [2:0] shemp,
|
||||
/* End autogenerated probe definitions */
|
||||
|
||||
input wire rxd,
|
||||
output logic txd);
|
||||
|
||||
/* Begin autogenerated parameters */
|
||||
localparam SAMPLE_WIDTH = 6;
|
||||
localparam SAMPLE_DEPTH = 4096;
|
||||
|
||||
localparam DATA_WIDTH = 8;
|
||||
localparam BAUDRATE = 115200;
|
||||
localparam CLK_FREQ_HZ = 100000000;
|
||||
|
||||
logic trigger;
|
||||
assign trigger = (larry && curly && ~moe);
|
||||
|
||||
logic [SAMPLE_WIDTH - 1 : 0] concat;
|
||||
assign concat = {larry, curly, moe, shemp};;
|
||||
/* End autogenerated parameters */
|
||||
|
||||
|
||||
// FIFO
|
||||
logic [7:0] fifo_data_in;
|
||||
logic fifo_input_ready;
|
||||
|
||||
logic fifo_request_output;
|
||||
logic [7:0] fifo_data_out;
|
||||
logic fifo_output_valid;
|
||||
|
||||
logic [11:0] fifo_size;
|
||||
logic fifo_empty;
|
||||
logic fifo_full;
|
||||
|
||||
fifo #(
|
||||
.WIDTH(SAMPLE_WIDTH),
|
||||
.DEPTH(SAMPLE_DEPTH)
|
||||
) fifo (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
|
||||
.data_in(fifo_data_in),
|
||||
.input_ready(fifo_input_ready),
|
||||
|
||||
.request_output(fifo_request_output),
|
||||
.data_out(fifo_data_out),
|
||||
.output_valid(fifo_output_valid),
|
||||
|
||||
.size(fifo_size),
|
||||
.empty(fifo_empty),
|
||||
.full(fifo_full));
|
||||
|
||||
// Serial interface
|
||||
logic tx_start;
|
||||
logic [7:0] tx_data;
|
||||
logic tx_busy;
|
||||
|
||||
logic [7:0] rx_data;
|
||||
logic rx_ready;
|
||||
logic rx_busy;
|
||||
|
||||
|
||||
uart_tx #(
|
||||
.DATA_WIDTH(DATA_WIDTH),
|
||||
.CLK_FREQ_HZ(CLK_FREQ_HZ),
|
||||
.BAUDRATE(BAUDRATE))
|
||||
tx (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
.start(tx_start),
|
||||
.data(tx_data),
|
||||
|
||||
.busy(tx_busy),
|
||||
.txd(txd));
|
||||
|
||||
uart_rx #(
|
||||
.DATA_WIDTH(DATA_WIDTH),
|
||||
.CLK_FREQ_HZ(CLK_FREQ_HZ),
|
||||
.BAUDRATE(BAUDRATE))
|
||||
rx (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
.rxd(rxd),
|
||||
|
||||
.data(rx_data),
|
||||
.ready(rx_ready),
|
||||
.busy(rx_busy));
|
||||
|
||||
|
||||
/* State Machine */
|
||||
/*
|
||||
|
||||
IDLE:
|
||||
- literally nothing is happening. the FIFO isn't being written to or read from. it should be empty.
|
||||
- an arm command over serial is what brings us into the ARM state
|
||||
|
||||
ARM:
|
||||
- popping things onto FIFO. if the fifo is halfway full, we pop them off too.
|
||||
- meeting the trigger condition is what moves us into the filing state
|
||||
|
||||
FILL:
|
||||
- popping things onto FIFO, until it's full. once it is full, we move into the downlinking state
|
||||
|
||||
DOWNLINK:
|
||||
- popping thing off of the FIFO until it's empty. once it's empty, we move back into the IDLE state
|
||||
*/
|
||||
|
||||
/* Downlink State Machine Controller */
|
||||
/*
|
||||
|
||||
- ila enters the downlink state
|
||||
- set fifo_output_request high for a clock cycle
|
||||
- when fifo_output_valid goes high, send fifo_data_out across the line
|
||||
- do nothing until tx_busy goes low
|
||||
- goto step 2
|
||||
|
||||
*/
|
||||
|
||||
logic [1:0] state;
|
||||
logic [2:0] downlink_fsm_state;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if(rst) begin
|
||||
state <= `IDLE;
|
||||
downlink_fsm_state <= 0;
|
||||
tx_data <= 0;
|
||||
tx_start <= 0;
|
||||
end
|
||||
else begin
|
||||
|
||||
case (state)
|
||||
`IDLE : begin
|
||||
fifo_input_ready <= 0;
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if (rx_ready && rx_data == `ARM_BYTE) state <= `ARM;
|
||||
|
||||
end
|
||||
|
||||
`ARM : begin
|
||||
// place samples into FIFO
|
||||
fifo_input_ready <= 1;
|
||||
fifo_data_in <= concat;
|
||||
|
||||
// remove old samples if we're more than halfway full
|
||||
fifo_request_output <= (fifo_size >= SAMPLE_DEPTH / 2);
|
||||
|
||||
if(trigger) state <= `FILL;
|
||||
end
|
||||
|
||||
`FILL : begin
|
||||
// place samples into FIFO
|
||||
fifo_input_ready <= 1;
|
||||
fifo_data_in <= concat;
|
||||
|
||||
// don't pop anything out the FIFO
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if(fifo_size == SAMPLE_DEPTH - 1) state <= `DOWNLINK;
|
||||
end
|
||||
|
||||
`DOWNLINK : begin
|
||||
// place no samples into FIFO
|
||||
fifo_input_ready <= 0;
|
||||
|
||||
|
||||
case (downlink_fsm_state)
|
||||
0 : begin
|
||||
if (~fifo_empty) begin
|
||||
fifo_request_output <= 1;
|
||||
downlink_fsm_state <= 1;
|
||||
end
|
||||
|
||||
else state <= `IDLE;
|
||||
end
|
||||
|
||||
1 : begin
|
||||
fifo_request_output <= 0;
|
||||
|
||||
if (fifo_output_valid) begin
|
||||
tx_data <= fifo_data_out;
|
||||
tx_start <= 1;
|
||||
downlink_fsm_state <= 2;
|
||||
end
|
||||
end
|
||||
|
||||
2 : begin
|
||||
tx_start <= 0;
|
||||
|
||||
if (~tx_busy && ~tx_start) downlink_fsm_state <= 0;
|
||||
end
|
||||
endcase
|
||||
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
324
manta
324
manta
|
|
@ -1,324 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
from sys import argv
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
import serial
|
||||
|
||||
debug = True
|
||||
version = '0.0.0'
|
||||
downlink_template = ''
|
||||
|
||||
def load_config(path):
|
||||
"""Take path to configuration file, and retun the configuration as a python list/dict object."""
|
||||
extension = path.split('.')[-1]
|
||||
|
||||
if 'json' in extension:
|
||||
with open(path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
return config
|
||||
|
||||
elif 'yaml' in extension or 'yml' in extension:
|
||||
with open(path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
return config
|
||||
|
||||
else:
|
||||
raise ValueError('Unable to recognize configuration file extension.')
|
||||
|
||||
def check_config(config):
|
||||
"""Takes a list/dict python object representing a core configuration and throws an error if it is misconfigured."""
|
||||
|
||||
assert 'downlink' in config or 'uplink' in config, 'No downlink or uplink specified.'
|
||||
|
||||
if 'downlink' in config:
|
||||
|
||||
dl = config['downlink']
|
||||
assert dl['sample_depth'], 'Downlink core specified, but sample_depth not specified.'
|
||||
assert dl['clock_freq'], 'Downlink core specified, but clock_freq not specified.'
|
||||
assert dl['probes'] and len(dl['probes']) > 0, 'Downlink core specified, but no probes specified.'
|
||||
assert dl['triggers'] and len(dl['triggers']) > 0, 'Downlink core specified, but no triggers specified.'
|
||||
|
||||
# confirm core clock is sufficiently fast
|
||||
prescaler = dl['clock_freq'] // config['uart']['baudrate']
|
||||
assert prescaler >= 2
|
||||
|
||||
# confirm actual baudrate and target baudrate are within 5%
|
||||
actual_baudrate = config['downlink']['clock_freq'] / prescaler
|
||||
baudrate_error = abs(actual_baudrate - config['uart']['baudrate']) / config['uart']['baudrate']
|
||||
assert baudrate_error <= 0.05, f'Unable to match target baudrate! Actual baudrate differs from target by {round(100*baudrate_error, 2)}%'
|
||||
|
||||
if debug:
|
||||
print(f'UART interface on debug core will the following configuration:')
|
||||
print(f' - target_baudrate: {config["uart"]["baudrate"]}')
|
||||
print(f' - prescaler: {prescaler}')
|
||||
print(f' - actual_baudrate: {round(actual_baudrate, 2)}')
|
||||
|
||||
if 'uplink' in config:
|
||||
raise NotImplementedError('Cannot check configuration validity for uplinks just yet!')
|
||||
|
||||
if 'uart' in config:
|
||||
uart = config['uart']
|
||||
|
||||
# confirm number of data bits is valid
|
||||
assert 'data' in uart, 'Number of data bits in UART interface not specified.'
|
||||
assert uart['data'] in [8, 7, 6, 5], 'Invalid number of data bits.'
|
||||
|
||||
# confirm number of stop bits is valid
|
||||
assert uart['stop'] in [1, 1.5, 2], 'Invalid number of stop bits.'
|
||||
|
||||
# confirm parity is valid
|
||||
assert uart['parity'] in ['none', 'even', 'odd', 'mark', 'space'], 'Invalid parity setting.'
|
||||
|
||||
def gen_downlink_core(config):
|
||||
buf = downlink_template
|
||||
dl = config['downlink']
|
||||
|
||||
# add timestamp
|
||||
timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
buf = buf.replace('@TIMESTAMP', timestamp)
|
||||
|
||||
# add user
|
||||
user = os.environ.get('USER', os.environ.get('USERNAME'))
|
||||
buf = buf.replace('@USER', user)
|
||||
|
||||
# add trigger
|
||||
trigger = [f'({trigger})' for trigger in dl['triggers']]
|
||||
trigger = ' || '.join(trigger)
|
||||
buf = buf.replace('@TRIGGER', trigger)
|
||||
|
||||
# add concat
|
||||
concat = [name for name in dl['probes']]
|
||||
concat = ', '.join(concat)
|
||||
concat = '{' + concat + '};'
|
||||
buf = buf.replace('@CONCAT', concat)
|
||||
|
||||
# add probes
|
||||
probe_verilog = []
|
||||
for name, width in dl['probes'].items():
|
||||
if width == 1:
|
||||
probe_verilog.append(f'input wire {name},')
|
||||
|
||||
else:
|
||||
probe_verilog.append(f'input wire [{width-1}:0] {name},')
|
||||
|
||||
probe_verilog = '\n\t'.join(probe_verilog)
|
||||
buf = buf.replace('@PROBES', probe_verilog)
|
||||
|
||||
# add sample width
|
||||
sample_width = sum([width for name, width in dl['probes'].items()])
|
||||
buf = buf.replace('@SAMPLE_WIDTH', sample_width)
|
||||
|
||||
# add sample depth
|
||||
buf = buf.replace('@SAMPLE_DEPTH', dl['sample_depth'])
|
||||
|
||||
# uart config
|
||||
buf = buf.replace('@DATA_WIDTH', str(config['uart']['data']))
|
||||
buf = buf.replace('@BAUDRATE', str(config['uart']['baudrate']))
|
||||
buf = buf.replace('@CLK_FREQ_HZ', str(config['uart']['clock_freq']))
|
||||
|
||||
return buf
|
||||
|
||||
def print_help():
|
||||
help = f"""
|
||||
Manta v{version} - An In-Situ Debugging Tool for Programmable Hardware
|
||||
|
||||
Supported commands:
|
||||
gen [config file] generate the core specified in the config file
|
||||
run [config file] run the core specified in the config file
|
||||
terminal [config file] present a minicom-like serial terminal with the UART settings in the config file
|
||||
ports list all available serial ports
|
||||
help display this help message
|
||||
ray display a splash screen (hehe...splash screen)
|
||||
"""
|
||||
print(help)
|
||||
|
||||
def print_ray():
|
||||
color_ray = f"""
|
||||
\033[96m (\.-./)
|
||||
\033[96m / \\
|
||||
\033[96m .' : '.
|
||||
\033[96m _.-'` ' `'-._ \033[34;49;1m | Manta v{version} \033[00m
|
||||
\033[96m .-' : '-. \033[34;49;3m | An In-Situ Debugging Tool for Programmable Hardware\033[00m
|
||||
\033[96m ,'_.._ . _.._', \033[34;49m | https://github.com/fischermoseley/manta\033[00m
|
||||
\033[96m '` `'-. ' .-'`
|
||||
\033[96m '. : .' \033[34;49;3m | fischerm [at] mit.edu\033[00m
|
||||
\033[96m \_. ._/
|
||||
\033[96m \ |^|
|
||||
\033[96m | | ;
|
||||
\033[96m \\'.___.' /
|
||||
\033[96m '-....-' \033[00m"""
|
||||
print(color_ray)
|
||||
|
||||
def setup_serial(ser, config):
|
||||
ser.baudrate = config['uart']['baudrate']
|
||||
ser.port = config['uart']['port']
|
||||
ser.timeout = config['uart']['timeout']
|
||||
|
||||
# setup number of data bits
|
||||
if config['uart']['data'] == 8:
|
||||
ser.bytesize = serial.EIGHTBITS
|
||||
|
||||
elif config['uart']['data'] == 7:
|
||||
ser.bytesize = serial.SEVENBITS
|
||||
|
||||
elif config['uart']['data'] == 6:
|
||||
ser.bytesize = serial.SIXBITS
|
||||
|
||||
elif config['uart']['data'] == 5:
|
||||
ser.bytesize = serial.FIVEBITS
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid number of data bits in UART configuration.")
|
||||
|
||||
# setup number of stop bits
|
||||
if config['uart']['stop'] == 1:
|
||||
ser.stopbits = serial.STOPBITS_ONE
|
||||
|
||||
elif config['uart']['stop'] == 1.5:
|
||||
ser.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||
|
||||
elif config['uart']['stop'] == 2:
|
||||
ser.stopbits = serial.STOPBITS_TWO
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid number of stop bits in UART configuration.")
|
||||
|
||||
# setup parity
|
||||
if config['uart']['parity'] == 'none':
|
||||
ser.parity = serial.PARITY_NONE
|
||||
|
||||
elif config['uart']['parity'] == 'even':
|
||||
ser.parity = serial.PARITY_EVEN
|
||||
|
||||
elif config['uart']['parity'] == 'odd':
|
||||
ser.parity = serial.PARITY_ODD
|
||||
|
||||
elif config['uart']['parity'] == 'mark':
|
||||
ser.parity = serial.PARITY_MARK
|
||||
|
||||
elif config['uart']['parity'] == 'space':
|
||||
ser.parity = serial.PARITY_SPACE
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid parity setting in UART configuration.")
|
||||
|
||||
def read_serial(config):
|
||||
# obtain bytestream from FPGA
|
||||
with serial.Serial() as ser:
|
||||
setup_serial(ser, config)
|
||||
ser.open()
|
||||
ser.flushInput()
|
||||
ser.write(b'\x30')
|
||||
data = ser.read(4096)
|
||||
|
||||
return data
|
||||
|
||||
def part_select(data, width):
|
||||
top, bottom = width
|
||||
|
||||
assert top >= bottom
|
||||
|
||||
mask = 2**(top - bottom + 1) - 1
|
||||
return (data >> bottom) & mask
|
||||
|
||||
def make_widths(config):
|
||||
# {probe0, probe1, probe2}
|
||||
# [12, 1, 3] should produce
|
||||
# [ (11,0) , (12, 12), (15,13) ]
|
||||
|
||||
widths = list(config['probes'].values())
|
||||
|
||||
parts = []
|
||||
for i, width in enumerate(widths):
|
||||
if (i == 0):
|
||||
parts.append( (width - 1, 0) )
|
||||
|
||||
else:
|
||||
parts.append( ((parts[i-1][1] + width) , (parts[i-1][1] + 1)) )
|
||||
|
||||
# reversing this list is a little bit of a hack, should fix/document
|
||||
return parts[::-1]
|
||||
|
||||
def export_waveform(config, data, path):
|
||||
extension = path.split('.')[-1]
|
||||
|
||||
if extension == 'vcd':
|
||||
from vcd import VCDWriter
|
||||
vcd_file = open(path, 'w')
|
||||
timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
|
||||
with VCDWriter(vcd_file, timescale='10 ns', date=timestamp, version = 'manta') as writer:
|
||||
|
||||
# add probes to vcd file
|
||||
vcd_probes = []
|
||||
for name, width in config['probes'].items():
|
||||
probe = writer.register_var('ila', name, 'wire', size = width)
|
||||
vcd_probes.append(probe)
|
||||
|
||||
# calculate bit widths for part selecting
|
||||
widths = make_widths(config)
|
||||
|
||||
# slice data, and dump to vcd file
|
||||
for timestamp, value in enumerate(data):
|
||||
for probe_num, probe in enumerate(vcd_probes):
|
||||
val = part_select(value, widths[probe_num])
|
||||
writer.change(probe, timestamp, val)
|
||||
|
||||
vcd_file.close()
|
||||
|
||||
else:
|
||||
raise NotImplementedError('More file formats to come!')
|
||||
|
||||
if __name__ == '__main__':
|
||||
# print help menu if no args passed or help menu requested
|
||||
if len(argv) == 1 or argv[1] == 'help':
|
||||
print_help()
|
||||
exit()
|
||||
|
||||
# open minicom-like serial terminal with given config
|
||||
elif argv[1] == 'terminal':
|
||||
assert len(argv) == 3, 'Not enough (or too many) config files specified.'
|
||||
|
||||
# TODO: make this work with a looser config file - it should work even if we just have a uart definition
|
||||
config = load_config(argv[2])
|
||||
check_config(config)
|
||||
|
||||
raise NotImplementedError('Miniterm console is still under development!')
|
||||
|
||||
# list available serial ports
|
||||
elif argv[1] == 'ports':
|
||||
import serial.tools.list_ports
|
||||
|
||||
for info in serial.tools.list_ports.comports():
|
||||
print(info)
|
||||
|
||||
# show splash screen
|
||||
elif argv[1] == 'ray':
|
||||
print_ray()
|
||||
exit()
|
||||
|
||||
# generate the specified core
|
||||
elif argv[1] == 'gen':
|
||||
assert len(argv) == 4, 'Wrong number of arguments, only a config file and output file must both be specified.'
|
||||
config = load_config(argv[2])
|
||||
check_config(config)
|
||||
|
||||
with open(argv[3], 'w') as f:
|
||||
f.write(gen_downlink_core(config))
|
||||
|
||||
# run the specified core
|
||||
elif argv[1] == 'run':
|
||||
assert len(argv) == 4, 'Wrong number of arguments, only a config file and output file must both be specified.'
|
||||
config = load_config(argv[2])
|
||||
check_config(config)
|
||||
|
||||
data = read_serial(config)
|
||||
export_waveform(config, data, argv[3])
|
||||
|
||||
else:
|
||||
print('Option not recognized.')
|
||||
print_help()
|
||||
24
manta.py
24
manta.py
|
|
@ -5,9 +5,25 @@ import yaml
|
|||
from datetime import datetime
|
||||
import serial
|
||||
|
||||
|
||||
debug = True
|
||||
version = '0.0.0'
|
||||
downlink_template = ''
|
||||
|
||||
|
||||
def load_source_files(path):
|
||||
"""concatenates the list of files provided into a single string"""
|
||||
source_files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||
source_files = [f for f in source_files if f.split('.')[-1] in ['sv', 'v']]
|
||||
|
||||
buf = ""
|
||||
for source_file in source_files:
|
||||
with open(path + source_file, 'r') as f:
|
||||
buf += f.read()
|
||||
|
||||
return buf
|
||||
|
||||
|
||||
downlink_template = load_source_files('src/')
|
||||
|
||||
def load_config(path):
|
||||
"""Take path to configuration file, and retun the configuration as a python list/dict object."""
|
||||
|
|
@ -109,15 +125,15 @@ def gen_downlink_core(config):
|
|||
|
||||
# add sample width
|
||||
sample_width = sum([width for name, width in dl['probes'].items()])
|
||||
buf = buf.replace('@SAMPLE_WIDTH', sample_width)
|
||||
buf = buf.replace('@SAMPLE_WIDTH', str(sample_width))
|
||||
|
||||
# add sample depth
|
||||
buf = buf.replace('@SAMPLE_DEPTH', dl['sample_depth'])
|
||||
buf = buf.replace('@SAMPLE_DEPTH', str(dl['sample_depth']))
|
||||
|
||||
# uart config
|
||||
buf = buf.replace('@DATA_WIDTH', str(config['uart']['data']))
|
||||
buf = buf.replace('@BAUDRATE', str(config['uart']['baudrate']))
|
||||
buf = buf.replace('@CLK_FREQ_HZ', str(config['uart']['clock_freq']))
|
||||
buf = buf.replace('@CLK_FREQ_HZ', str(dl['clock_freq']))
|
||||
|
||||
return buf
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue