diff --git a/.gitignore b/.gitignore index ce383cb..00955ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +*.log +*.bit *.vcd *.out diff --git a/examples/counter/src/debug.sv b/examples/counter/src/debug.sv new file mode 100644 index 0000000..f620eb0 --- /dev/null +++ b/examples/counter/src/debug.sv @@ -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 \ No newline at end of file diff --git a/examples/counter/src/ila.sv b/examples/counter/src/ila.sv deleted file mode 100644 index f1babbf..0000000 --- a/examples/counter/src/ila.sv +++ /dev/null @@ -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 \ No newline at end of file diff --git a/manta b/manta deleted file mode 100755 index 929f3aa..0000000 --- a/manta +++ /dev/null @@ -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() diff --git a/manta.py b/manta.py index 0e66591..8dcfcdc 100644 --- a/manta.py +++ b/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