diff options
| -rw-r--r-- | Makefile.inner | 6 | ||||
| -rw-r--r-- | fw/ds18b20.c | 156 | ||||
| -rw-r--r-- | fw/pwm.c | 53 | ||||
| -rw-r--r-- | fw/timer0_test.c | 73 | ||||
| -rw-r--r-- | hdl/ALU.vhd | 9 | ||||
| -rw-r--r-- | hdl/PIC.vhd | 93 | ||||
| -rw-r--r-- | hdl/common.vhd | 2 | ||||
| -rw-r--r-- | hdl/decoder.vhd | 7 | ||||
| -rw-r--r-- | hdl/dpram.vhd | 128 | ||||
| -rw-r--r-- | hdl/hdl-prj.json | 1 | ||||
| -rw-r--r-- | hdl/onewire.vhd | 195 | ||||
| -rw-r--r-- | hdl/pic_top.vhd | 7 | ||||
| -rw-r--r-- | hdl/state_machine.vhd | 32 | ||||
| -rw-r--r-- | hdl/timer0.vhd | 131 | ||||
| -rw-r--r-- | hex2rom.py | 12 | ||||
| -rw-r--r-- | primer25k.cst | 5 |
16 files changed, 864 insertions, 46 deletions
diff --git a/Makefile.inner b/Makefile.inner index 957843c..5838b33 100644 --- a/Makefile.inner +++ b/Makefile.inner @@ -15,6 +15,8 @@ SYN_FILES = \ $(HDL_DIR)/decoder.vhd \ $(HDL_DIR)/fifo.vhd \ $(HDL_DIR)/uart.vhd \ + $(HDL_DIR)/onewire.vhd \ + $(HDL_DIR)/timer0.vhd \ $(HDL_DIR)/ALU.vhd \ $(HDL_DIR)/stack.vhd \ $(HDL_DIR)/dpram.vhd \ @@ -30,6 +32,8 @@ SIM_FILES = \ $(HDL_DIR)/hexfile_reader.vhd \ $(HDL_DIR)/fifo.vhd \ $(HDL_DIR)/uart.vhd \ + $(HDL_DIR)/onewire.vhd \ + $(HDL_DIR)/timer0.vhd \ $(HDL_DIR)/ALU.vhd \ $(HDL_DIR)/stack.vhd \ $(HDL_DIR)/dpram.vhd \ @@ -48,7 +52,7 @@ $(BUILD): # Synthesize $(BUILD)/$(TOP).json: $(SYN_FILES) | $(BUILD) ghdl -a $(STD) --workdir=$(BUILD) $(SYN_FILES) - yosys $(MODULE) -p "ghdl $(STD) --workdir=$(BUILD) $(TOP); memory_map; synth_gowin -json $@ -family gw5a" + yosys $(MODULE) -p "ghdl $(STD) --workdir=$(BUILD) $(TOP); synth_gowin -json $@ -family gw5a -nolutram" # Place and route $(BUILD)/$(TOP)_pnr.json: $(BUILD)/$(TOP).json $(CST) diff --git a/fw/ds18b20.c b/fw/ds18b20.c new file mode 100644 index 0000000..0b57732 --- /dev/null +++ b/fw/ds18b20.c @@ -0,0 +1,156 @@ +#include <pic16f84a.h> + +typedef unsigned char uint8_t; + +__sfr __at(0x70) UART_TX; +__sfr __at(0x72) UART_STATUS; + +__sfr __at(0x73) OW_DATA; +__sfr __at(0x74) OW_CMD; +__sfr __at(0x75) OW_STATUS; + +#define OW_RESET 0x00 +#define OW_WRITE 0x01 +#define OW_READ 0x02 + +#define OW_BUSY 0x01 +#define OW_PRESENCE 0x02 + +#define CMD_SKIP_ROM 0xCC +#define CMD_CONVERT_T 0x44 +#define CMD_READ_SCRATCH 0xBE + +/* + * Fractional nibble lookup: each step is 0.0625 deg C. + * Tables give the two decimal digits (tenths, hundredths) truncated. + * e.g. frac=1 -> 0.0625 -> prints ".06" + * frac=8 -> 0.5000 -> prints ".50" + */ +static const uint8_t frac_tens[16] = {0,0,1,1,2,3,3,4,5,5,6,6,7,8,8,9}; +static const uint8_t frac_hund[16] = {0,6,2,8,5,1,7,4,0,6,2,8,5,1,7,3}; + +/* + * Timer0 delay. OPTION_REG 0x07: T0CS=0 (Fosc/4), PSA=0, PS=111 (1:256). + * At 50 MHz: overflow every 256*256 / (50M/4) = 5.24 ms. + * 143 overflows ≈ 750 ms. + */ +#define STATUS_RP0 0x20 +#define T0IF 0x04 + +static void wait_overflows(uint8_t n) { + INTCON &= ~T0IF; + while (n--) { + while (!(INTCON & T0IF)); + INTCON &= ~T0IF; + } +} + +static void delay_750ms(void) { wait_overflows(143); } + +static void uart_send(uint8_t b) { + while (UART_STATUS & 0x01); + UART_TX = b; +} + +static void uart_puts(const char *s) { + while (*s) uart_send((uint8_t)*s++); +} + +static void ow_wait(void) { + while (OW_STATUS & OW_BUSY); +} + +static void ow_write_byte(uint8_t b) { + OW_DATA = b; + OW_CMD = OW_WRITE; + ow_wait(); +} + +static uint8_t ow_read_byte(void) { + OW_CMD = OW_READ; + ow_wait(); + return OW_DATA; +} + +static void print_uint8(uint8_t v) { + uint8_t started = 0; + if (v >= 200) { uart_send('2'); v -= 200; started = 1; } + else if (v >= 100) { uart_send('1'); v -= 100; started = 1; } + { + uint8_t t = '0'; + while (v >= 10) { v -= 10; t++; } + if (started || t != '0') uart_send(t); + } + uart_send(v + '0'); +} + +void main(void) { + uint8_t lo, hi, sign, temp_int, frac; + uint8_t i; + + /* Configure Timer0: bank 1, OPTION_REG = 0x07 (Fosc/4, prescaler 1:256) */ + STATUS |= STATUS_RP0; + TMR0 = 0x07; + STATUS &= ~STATUS_RP0; + + uart_puts("\x1B[2J\x1B[H"); + uart_puts("DS18B20 Temperature\r\n"); + uart_puts("-------------------\r\n"); + + while (1) { + /* Reset */ + OW_CMD = OW_RESET; + ow_wait(); + if (!(OW_STATUS & OW_PRESENCE)) { + uart_puts("No sensor\r\n"); + delay_750ms(); + continue; + } + + /* Start temperature conversion */ + ow_write_byte(CMD_SKIP_ROM); + ow_write_byte(CMD_CONVERT_T); + delay_750ms(); + + /* Reset and read scratchpad */ + OW_CMD = OW_RESET; + ow_wait(); + if (!(OW_STATUS & OW_PRESENCE)) { + uart_puts("Lost sensor\r\n"); + continue; + } + ow_write_byte(CMD_SKIP_ROM); + ow_write_byte(CMD_READ_SCRATCH); + + lo = ow_read_byte(); /* scratchpad byte 0: temp LSB */ + hi = ow_read_byte(); /* scratchpad byte 1: temp MSB */ + for (i = 0; i < 7; i++) ow_read_byte(); /* discard remaining bytes */ + + /* + * DS18B20 12-bit format: + * sign = hi bit 7 (and sign-extended through hi[7:4]) + * integer part = hi[3:0]:lo[7:4] + * fractional nibble = lo[3:0] (each bit = 0.0625 deg C) + * + * Negative values are two's complement of the 16-bit word. + */ + sign = hi & 0x80; + if (sign) { + lo = ~lo; + hi = ~hi; + if (lo == 0xFF) { lo = 0; hi++; } + else { lo++; } + } + + temp_int = ((hi & 0x0F) << 4) | (lo >> 4); + frac = lo & 0x0F; + + uart_puts("\rTemp: "); + if (sign) uart_send('-'); + print_uint8(temp_int); + uart_send('.'); + uart_send('0' + frac_tens[frac]); + uart_send('0' + frac_hund[frac]); + uart_puts(" C\r\n"); + } +} diff --git a/fw/pwm.c b/fw/pwm.c new file mode 100644 index 0000000..60f8b5c --- /dev/null +++ b/fw/pwm.c @@ -0,0 +1,53 @@ +#include <pic16f84a.h> + +typedef unsigned char uint8_t; + +/* + * Breathing LED via bit-bang PWM on PORTA(0) and PORTB(0). + * + * The PWM period is 256 counts of the inner counter 'pwm'. + * Each duty level is held for HOLD_PERIODS full PWM periods before + * stepping, giving a smooth fade up then down (heartbeat pattern). + * + * At 50 MHz with SDCC's output (~10 instructions/iteration) the inner + * loop runs at roughly 5 MHz, so one 256-count PWM period ≈ 51 µs + * → ~20 kHz PWM. With HOLD_PERIODS = 32 each brightness step lasts + * ~1.6 ms and the full 0→255→0 sweep takes ~820 ms — a natural breath. + * Tweak HOLD_PERIODS to taste. + */ + +#define HOLD_PERIODS 32 + +void main(void) { + uint8_t duty = 0; /* current duty cycle, 0..255 */ + uint8_t dir = 1; /* 1 = ramping up, 0 = ramping down */ + uint8_t pwm = 0; /* free-running counter within one period */ + uint8_t periods = 0; /* how many full periods at this duty level */ + + while (1) { + /* PWM compare: LED on when we're inside the duty window */ + if (pwm < duty) { + PORTA = 0x01; + PORTB = 0x01; + } else { + PORTA = 0x00; + PORTB = 0x00; + } + + pwm++; /* wraps 255→0, completing one 256-count PWM period */ + + if (pwm == 0) { + periods++; + if (periods == HOLD_PERIODS) { + periods = 0; + if (dir) { + duty++; + if (duty == 255) dir = 0; /* peak: start ramping down */ + } else { + duty--; + if (duty == 0) dir = 1; /* floor: start ramping up */ + } + } + } + } +} diff --git a/fw/timer0_test.c b/fw/timer0_test.c new file mode 100644 index 0000000..ef5e622 --- /dev/null +++ b/fw/timer0_test.c @@ -0,0 +1,73 @@ +#include <pic16f84a.h> + +typedef unsigned char uint8_t; + +__sfr __at(0x70) UART_TX; +__sfr __at(0x72) UART_STATUS; + +/* + * OPTION_REG 0x07: T0CS=0 (Fosc/4), PSA=0, PS=111 (1:256) + * At 50 MHz: overflow every 256*256 / (50M/4) = 5.24 ms → ~191 ovf/sec + */ +#define STATUS_RP0 0x20 +#define T0IF 0x04 /* INTCON bit 2 */ +#define T0IE 0x20 /* INTCON bit 5 */ +#define GIE 0x80 /* INTCON bit 7 */ +#define OVF_PER_SEC 191 + +static volatile uint8_t ovf_count = 0; + +void isr(void) __interrupt(0) { + if (INTCON & T0IF) { + ovf_count++; + INTCON &= ~T0IF; + } +} + +static void uart_send(uint8_t b) { + while (UART_STATUS & 0x01); + UART_TX = b; +} + +static void uart_puts(const char *s) { + while (*s) uart_send((uint8_t)*s++); +} + +static void print_uint8(uint8_t v) { + uint8_t started = 0; + if (v >= 200) { uart_send('2'); v -= 200; started = 1; } + else if (v >= 100) { uart_send('1'); v -= 100; started = 1; } + { + uint8_t t = '0'; + while (v >= 10) { v -= 10; t++; } + if (started || t != '0') uart_send(t); + } + uart_send(v + '0'); +} + +void main(void) { + uint8_t seconds = 0; + + /* Configure OPTION_REG (bank 1, same 7-bit addr as TMR0) */ + STATUS |= STATUS_RP0; + TMR0 = 0x07; + STATUS &= ~STATUS_RP0; + + TMR0 = 0; + INTCON = T0IE | GIE; /* enable Timer0 overflow interrupt */ + + uart_puts("\x1B[2J\x1B[H"); + uart_puts("Timer0 test\r\n"); + uart_puts("-----------\r\n"); + + while (1) { + ovf_count = 0; + while (ovf_count < OVF_PER_SEC); /* ISR bumps ovf_count */ + + uart_puts("t = "); + print_uint8(seconds); + uart_puts(" s\r\n"); + PORTA ^= 0x01; + seconds++; + } +} diff --git a/hdl/ALU.vhd b/hdl/ALU.vhd index b7c0344..4d3a065 100644 --- a/hdl/ALU.vhd +++ b/hdl/ALU.vhd @@ -206,10 +206,11 @@ begin - when CALL => null; - when GOTO => null; - when RETLW => result <= b; - when RETUR => null; + when CALL => null; + when GOTO => null; + when RETLW => result <= b; + when RETUR => null; + when RETFIE => null; end case; end process; diff --git a/hdl/PIC.vhd b/hdl/PIC.vhd index a31bec7..7471add 100644 --- a/hdl/PIC.vhd +++ b/hdl/PIC.vhd @@ -22,7 +22,10 @@ entity PIC is -- UART physical pins uart_tx : out std_logic; - uart_rx : in std_logic + uart_rx : in std_logic; + + -- 1-Wire physical pin + ow_pin : inout std_logic ); end entity PIC; @@ -34,12 +37,13 @@ architecture rtl of PIC is signal alu_skip : std_logic; signal mem_q : std_logic_vector(7 downto 0); - signal mem_qstatus : std_logic_vector(2 downto 0); + signal mem_qstatus : std_logic_vector(7 downto 0); signal mem_we : std_logic; signal mem_re : std_logic; signal bit_select : std_logic_vector(2 downto 0); signal addr : std_logic_vector(6 downto 0); + signal mem_addr : std_logic_vector(7 downto 0); signal sm_data : std_logic_vector(7 downto 0); signal sm_pc : std_logic_vector(12 downto 0); signal we_w : std_logic; @@ -64,6 +68,26 @@ architecture rtl of PIC is signal uart_rx_empty : std_logic; signal uart_rx_full : std_logic; + -- 1-Wire <-> dpram bridge signals + signal ow_tx_byte : std_logic_vector(7 downto 0); + signal ow_op : std_logic_vector(1 downto 0); + signal ow_start : std_logic; + signal ow_rx_byte : std_logic_vector(7 downto 0); + signal ow_presence : std_logic; + signal ow_busy : std_logic; + + -- Interrupt bridge signals + signal irq_pending : std_logic; + signal gie_clear : std_logic; + signal gie_set : std_logic; + + -- Timer0 bridge signals + signal t0if : std_logic; + signal tmr0_val : std_logic_vector(7 downto 0); + signal tmr0_we_sig : std_logic; + signal tmr0_in_sig : std_logic_vector(7 downto 0); + signal option_reg : std_logic_vector(7 downto 0); + begin work_reg <= W; @@ -71,6 +95,9 @@ begin instruction_return <= instr_ret; status <= alu_status; + -- banking: RP0 (STATUS bit 5) selects bank 0 or bank 1 + mem_addr <= mem_qstatus(5) & addr; + b_operand_select : process(use_literal, sm_data, mem_q) begin if use_literal = '1' then @@ -110,7 +137,10 @@ begin stack_din => stack_din, stack_dout => stack_dout, alu_skip => alu_skip, - alu_result => alu_result + alu_result => alu_result, + irq_pending => irq_pending, + gie_clear => gie_clear, + gie_set => gie_set ); alu : entity work.alu(rtl) @@ -119,7 +149,7 @@ begin b => b_operand, op => alu_operation, bit_select => bit_select, - status_in => mem_qstatus, + status_in => mem_qstatus(2 downto 0), result => alu_result, status => alu_status, skip => alu_skip @@ -136,14 +166,14 @@ begin ); mem : entity work.dpram(rtl) - generic map (DEPTH => 128) + generic map (DEPTH => 256) port map ( clk => clk, we => mem_we, re => mem_re, d => alu_result, d_status => "00000" & alu_status, - addr => addr, + addr => mem_addr, q => mem_q, q_status => mem_qstatus, port_a => port_a, @@ -155,7 +185,56 @@ begin uart_rx_byte => uart_rx_byte, uart_rx_rd_en => uart_rx_rd_en, uart_rx_empty => uart_rx_empty, - uart_rx_full => uart_rx_full + uart_rx_full => uart_rx_full, + ow_tx_byte => ow_tx_byte, + ow_op => ow_op, + ow_start => ow_start, + ow_rx_byte => ow_rx_byte, + ow_presence => ow_presence, + ow_busy => ow_busy, + tmr0_val => tmr0_val, + tmr0_we => tmr0_we_sig, + tmr0_in => tmr0_in_sig, + option_reg => option_reg, + t0if_set => t0if, + gie_clear => gie_clear, + gie_set => gie_set, + irq_pending => irq_pending + ); + + ow_inst : entity work.onewire + generic map ( + CLK_FREQ => CLK_FREQ + ) + port map ( + clk => clk, + reset => reset, + ow_pin => ow_pin, + start => ow_start, + op => ow_op, + tx_byte => ow_tx_byte, + rx_byte => ow_rx_byte, + presence => ow_presence, + busy => ow_busy, + done => open + ); + + tmr0_inst : entity work.timer0(rtl) + generic map ( + CLK_FREQ => CLK_FREQ + ) + port map ( + clk => clk, + reset => reset, + t0cki => '0', + t0cs => option_reg(5), + t0se => option_reg(4), + psa => option_reg(3), + ps => option_reg(2 downto 0), + tmr0_we => tmr0_we_sig, + tmr0_in => tmr0_in_sig, + tmr0_out => tmr0_val, + t0if => t0if ); uart_inst : entity work.uart diff --git a/hdl/common.vhd b/hdl/common.vhd index c986ec5..4925f3e 100644 --- a/hdl/common.vhd +++ b/hdl/common.vhd @@ -2,7 +2,7 @@ package alu_types is type alu_op is (ADDWF, ANDWF, ADDLW, ANDLW, BCF, BTFSC, BSF, BTFSS, CLRF, CLRW, COMF, DECF, DECFSZ, INCF, INCFSZ, IORLW, MOVF, MOVWF, CALL, - GOTO, MOVLW, RETLW, RETUR, IORWF, NOP, + GOTO, MOVLW, RETLW, RETUR, RETFIE, IORWF, NOP, RLF, RRF, SUBLW, SUBWF, SWAPF, XORLW, XORWF); -- Status register bits: Z=2, DC=1, C=0 diff --git a/hdl/decoder.vhd b/hdl/decoder.vhd index 5a1eb2c..07e785d 100644 --- a/hdl/decoder.vhd +++ b/hdl/decoder.vhd @@ -63,8 +63,9 @@ package body decoder is pure function instruction_get_class(opc : std_logic_vector(13 downto 0)) return instruction_class_t is begin - -- RETURN (0x0008) has bits[13:12]="00" but must be treated as LITERAL_OP + -- RETURN (0x0008) and RETFIE (0x0009) have bits[13:12]="00" if opc = "00000000001000" then return LITERAL_OP; end if; + if opc = "00000000001001" then return LITERAL_OP; end if; case opc(13 downto 12) is -- byte-oriented file-register operations when "00" => @@ -130,8 +131,8 @@ package body decoder is when LITERAL_OP => -- fixed full-word patterns first (fall inside 00xx space) - -- if opc(11 downto 0) = "000000000001" then return RETFIE; - if opc(11 downto 0) = "000000001000" then return RETUR; + if opc(11 downto 0) = "000000001001" then return RETFIE; + elsif opc(11 downto 0) = "000000001000" then return RETUR; -- elsif opc(11 downto 0) = "000000000011" then return SLEEP; -- elsif opc(11 downto 0) = "000000000100" then return CLRWDT; else diff --git a/hdl/dpram.vhd b/hdl/dpram.vhd index 44d7adf..92b57f2 100644 --- a/hdl/dpram.vhd +++ b/hdl/dpram.vhd @@ -2,23 +2,27 @@ library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; --- Data memory with UART memory-mapped registers. --- Addresses 0x70-0x72 are used (unimplemented in real PIC16F84A, no header conflict): +-- Data memory with UART and 1-Wire memory-mapped registers. +-- Addresses 0x70-0x72 are UART (unimplemented in real PIC16F84A, no header conflict): -- 0x70 UART_TX write: send byte (held until tx_done) -- 0x71 UART_RX read: current FIFO head (FWFT), pulses rd_en to pop -- 0x72 UART_STATUS read: "00000" & rx_full & rx_empty & tx_busy +-- Addresses 0x73-0x75 are 1-Wire: +-- 0x73 OW_DATA write: tx_byte to send; read: rx_byte received +-- 0x74 OW_CMD write: bits[1:0]=op (00=reset,01=write,10=read), any write pulses start +-- 0x75 OW_STATUS read: "000000" & presence & busy entity dpram is - generic (DEPTH : positive := 128); + generic (DEPTH : positive := 256); port ( clk : in std_logic; we : in std_logic; re : in std_logic; d : in std_logic_vector(7 downto 0); d_status : in std_logic_vector(7 downto 0); - addr : in std_logic_vector(6 downto 0); + addr : in std_logic_vector(7 downto 0); q : out std_logic_vector(7 downto 0); - q_status : out std_logic_vector(2 downto 0); + q_status : out std_logic_vector(7 downto 0); port_a : out std_logic_vector(7 downto 0); port_b : out std_logic_vector(7 downto 0); @@ -31,7 +35,29 @@ entity dpram is uart_rx_byte : in std_logic_vector(7 downto 0); uart_rx_rd_en : out std_logic := '0'; uart_rx_empty : in std_logic; - uart_rx_full : in std_logic + uart_rx_full : in std_logic; + + -- 1-Wire interface + ow_tx_byte : out std_logic_vector(7 downto 0) := (others => '0'); + ow_op : out std_logic_vector(1 downto 0) := "00"; + ow_start : out std_logic := '0'; + ow_rx_byte : in std_logic_vector(7 downto 0); + ow_presence : in std_logic; + ow_busy : in std_logic; + + -- Timer0 interface + tmr0_val : in std_logic_vector(7 downto 0); -- current TMR0 value + tmr0_we : out std_logic := '0'; -- CPU writing TMR0 + tmr0_in : out std_logic_vector(7 downto 0) := (others => '0'); + + -- OPTION_REG (bank 1, 0x81) — read by timer0 + option_reg : out std_logic_vector(7 downto 0); + + -- Interrupt control (INTCON at 0x0B) + t0if_set : in std_logic; -- timer0 overflow: sets INTCON.T0IF (bit 2) + gie_clear : in std_logic; -- interrupt ack: clears INTCON.GIE (bit 7) + gie_set : in std_logic; -- RETFIE: sets INTCON.GIE (bit 7) + irq_pending : out std_logic -- GIE & T0IE & T0IF ); end entity dpram; @@ -42,10 +68,13 @@ architecture rtl of dpram is signal mem : mem_t := (others => (others => '0')); signal uart_tx_pending : std_logic := '0'; + signal ow_start_i : std_logic := '0'; + signal ow_op_i : std_logic_vector(1 downto 0) := "00"; + signal ow_tx_byte_i : std_logic_vector(7 downto 0) := (others => '0'); begin - -- STATUS output is combinatorial - q_status <= mem(16#03#)(2 downto 0); + -- STATUS output is combinatorial (full 8-bit register) + q_status <= mem(16#03#); port_a <= mem(16#05#); port_b <= mem(16#06#); @@ -53,11 +82,21 @@ begin -- tx_send is high while a transmission is in progress uart_tx_send <= uart_tx_pending; + ow_start <= ow_start_i; + ow_op <= ow_op_i; + ow_tx_byte <= ow_tx_byte_i; + + option_reg <= mem(16#81#); + irq_pending <= mem(16#0B#)(7) and mem(16#0B#)(5) and mem(16#0B#)(2); + + tmr0_we <= '1' when we = '1' and addr = "00000001" else '0'; + tmr0_in <= d; + -- SR: set when CPU writes to 0x08, clear when UART asserts tx_done tx_ctrl : process(clk) begin if rising_edge(clk) then - if we = '1' and addr = "1110000" then -- 0x70 UART_TX + if we = '1' and addr = "01110000" then -- 0x70 UART_TX uart_tx_byte <= d; uart_tx_pending <= '1'; elsif uart_tx_done = '1' then @@ -66,18 +105,48 @@ begin end if; end process tx_ctrl; + ow_ctrl : process(clk) + begin + if rising_edge(clk) then + ow_start_i <= '0'; + if we = '1' then + if addr = "01110011" then -- 0x73 OW_DATA + ow_tx_byte_i <= d; + elsif addr = "01110100" then -- 0x74 OW_CMD + ow_op_i <= d(1 downto 0); + ow_start_i <= '1'; + end if; + end if; + end if; + end process ow_ctrl; + read : process(clk) begin if rising_edge(clk) then uart_rx_rd_en <= '0'; -- default: no pop if re = '1' then - if addr = "1110001" then -- 0x71 UART_RX - q <= uart_rx_byte; -- FWFT head, valid combinatorially + if addr = "00000000" or + addr = "10000000" then -- 0x00/0x80 INDF: indirect via FSR + q <= mem(to_integer(unsigned(mem(16#04#)))); + elsif addr = "00000001" then -- 0x01 TMR0 (bank 0, read from timer0) + q <= tmr0_val; + elsif addr = "00001011" or + addr = "10001011" then -- 0x0B/0x8B INTCON (mirrored) + q <= mem(16#0B#); + elsif addr = "01110001" then -- 0x71 UART_RX + q <= uart_rx_byte; if uart_rx_empty = '0' then - uart_rx_rd_en <= '1'; -- advance FIFO pointer + uart_rx_rd_en <= '1'; end if; - elsif addr = "1110010" then -- 0x72 UART_STATUS + elsif addr = "01110010" then -- 0x72 UART_STATUS q <= "00000" & uart_rx_full & uart_rx_empty & uart_tx_pending; + elsif addr = "01110011" then -- 0x73 OW_DATA + q <= ow_rx_byte; + elsif addr = "01110101" then -- 0x75 OW_STATUS + q <= "000000" & ow_presence & ow_busy; + elsif addr = "00000011" or + addr = "10000011" then -- 0x03/0x83 STATUS (mirrored) + q <= mem(16#03#); else q <= mem(to_integer(unsigned(addr))); end if; @@ -89,21 +158,40 @@ begin begin if rising_edge(clk) then if we_status = '1' then - mem(16#03#) <= d_status; + -- only Z/DC/C (bits 2:0) come from ALU; preserve RP0, TO, PD etc. + mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0); end if; if we = '1' then - if addr = "0000011" then -- 0x03 STATUS: direct write - mem(16#03#) <= d; - elsif addr = "1110000" or -- 0x70 UART_TX - addr = "1110001" or -- 0x71 UART_RX (read-only but handled gracefully) - addr = "1110010" then -- 0x72 UART_STATUS (read-only) - -- UART-mapped addresses: don't write to RAM; still update STATUS + if addr = "00000000" or + addr = "10000000" then -- 0x00/0x80 INDF: indirect via FSR mem(16#03#) <= d_status; + mem(to_integer(unsigned(mem(16#04#)))) <= d; + elsif addr = "00000001" then -- 0x01 TMR0: routed to timer0, not RAM + mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0); + elsif addr = "00001011" or + addr = "10001011" then -- 0x0B/0x8B INTCON (mirrored): direct write + mem(16#0B#) <= d; + elsif addr = "00000011" or + addr = "10000011" then -- 0x03/0x83 STATUS (mirrored): direct write + mem(16#03#) <= d; + elsif addr = "01110000" or -- 0x70 UART_TX + addr = "01110001" or -- 0x71 UART_RX (read-only) + addr = "01110010" or -- 0x72 UART_STATUS (read-only) + addr = "01110011" or -- 0x73 OW_DATA + addr = "01110100" or -- 0x74 OW_CMD + addr = "01110101" then -- 0x75 OW_STATUS (read-only) + -- peripheral-mapped addresses: don't write to RAM + mem(16#03#) <= mem(16#03#)(7 downto 3) & d_status(2 downto 0); else mem(16#03#) <= d_status; mem(to_integer(unsigned(addr))) <= d; end if; end if; + + -- Hardware INTCON modifications (last = highest priority) + if t0if_set = '1' then mem(16#0B#)(2) <= '1'; end if; + if gie_clear = '1' then mem(16#0B#)(7) <= '0'; end if; + if gie_set = '1' then mem(16#0B#)(7) <= '1'; end if; end if; end process write; diff --git a/hdl/hdl-prj.json b/hdl/hdl-prj.json index b557c2b..7a2e576 100644 --- a/hdl/hdl-prj.json +++ b/hdl/hdl-prj.json @@ -14,6 +14,7 @@ { "file": "ALU.vhd", "language": "vhdl" }, { "file": "stack.vhd", "language": "vhdl" }, { "file": "dpram.vhd", "language": "vhdl" }, + { "file": "onewire.vhd", "language": "vhdl" }, { "file": "state_machine.vhd", "language": "vhdl" }, { "file": "PIC.vhd", "language": "vhdl" }, { "file": "prog_rom.vhd", "language": "vhdl" }, diff --git a/hdl/onewire.vhd b/hdl/onewire.vhd new file mode 100644 index 0000000..440d34d --- /dev/null +++ b/hdl/onewire.vhd @@ -0,0 +1,195 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity onewire is + generic ( + CLK_FREQ : positive := 50_000_000 + ); + port ( + clk : in std_logic; + reset : in std_logic; + + ow_pin : inout std_logic; -- 4.7 kohm pull-up to 3.3 V externally + + start : in std_logic; + op : in std_logic_vector(1 downto 0); -- "00" reset, "01" write, "10" read + tx_byte : in std_logic_vector(7 downto 0); + rx_byte : out std_logic_vector(7 downto 0); + presence : out std_logic; + busy : out std_logic; + done : out std_logic + ); +end entity onewire; + +architecture rtl of onewire is + + constant us : positive := CLK_FREQ / 1_000_000; + + constant RESET_LOW_TICKS : positive := 480 * us; + constant PRESENCE_WAIT_TICKS : positive := 15 * us; + constant PRESENCE_SAMPLE_END : positive := 75 * us; + constant RESET_RECOVER_TICKS : positive := 480 * us; + constant WRITE0_LOW_TICKS : positive := 60 * us; + constant WRITE1_LOW_TICKS : positive := 6 * us; + constant SLOT_TICKS : positive := 70 * us; + constant READ_INIT_TICKS : positive := 6 * us; + constant READ_SAMPLE_TICKS : positive := 15 * us; + + type state_t is ( + IDLE, + RST_PULL, RST_RELEASE, RST_SAMPLE, RST_RECOVER, + WR_PULL, WR_RELEASE, + RD_PULL, RD_RELEASE, RD_SAMPLE, RD_RECOVER, + BYTE_NEXT + ); + + signal state : state_t := IDLE; + signal ow_drive : std_logic := '0'; + signal ow_sense : std_logic; + signal timer : natural range 0 to RESET_LOW_TICKS + 1 := 0; + signal bit_index : natural range 0 to 8 := 0; + signal shift_reg : std_logic_vector(7 downto 0) := (others => '0'); + signal cur_bit : std_logic := '0'; + signal is_read : std_logic := '0'; + signal rx_byte_i : std_logic_vector(7 downto 0) := (others => '0'); + + signal presence_internal : std_logic := '0'; +begin + + ow_pin <= '0' when ow_drive = '1' else 'Z'; + ow_sense <= ow_pin; + + presence <= presence_internal; + + rx_byte <= rx_byte_i; + busy <= '0' when state = IDLE else '1'; + + process(clk) + begin + if rising_edge(clk) then + done <= '0'; + timer <= timer + 1; + + if reset = '1' then + state <= IDLE; + ow_drive <= '0'; + timer <= 0; + else + case state is + when IDLE => + timer <= 0; + + if start = '1' then + presence_internal <= '0'; + case op is + -- reset + when "00" => + timer <= 0; + state <= RST_PULL; + + -- write + when "01" => + state <= WR_PULL; + shift_reg <= tx_byte; + bit_index <= 0; + is_read <= '0'; + + -- read + when "10" => + state <= RD_PULL; + bit_index <= 0; + is_read <= '1'; + + when others => null; + end case; + end if; + + when RST_PULL => + ow_drive <= '1'; + + if timer >= RESET_LOW_TICKS then + state <= RST_RELEASE; + end if; + + + when RST_RELEASE => + ow_drive <= '0'; + timer <= 0; + state <= RST_SAMPLE; + + when RST_SAMPLE => + if ow_sense = '0' then + presence_internal <= '1'; + end if; + + if (ow_sense = '1' and presence_internal = '1') or + timer >= PRESENCE_SAMPLE_END then + state <= RST_RECOVER; + end if; + + when RST_RECOVER => + -- wait for 480us since RST_RELEASE + if timer >= RESET_RECOVER_TICKS then + done <= '1'; + state <= IDLE; + end if; + + when WR_PULL => + ow_drive <= '1'; + if (shift_reg(bit_index) = '1' and timer >= WRITE1_LOW_TICKS) or + (shift_reg(bit_index) = '0' and timer >= WRITE0_LOW_TICKS) then + state <= WR_RELEASE; + end if; + + when WR_RELEASE => + ow_drive <= '0'; + + if timer >= SLOT_TICKS then + state <= BYTE_NEXT; + end if; + + when RD_PULL => + ow_drive <= '1'; + + if timer >= READ_INIT_TICKS then + state <= RD_RELEASE; + end if; + + when RD_RELEASE => + ow_drive <= '0'; + + if timer >= READ_INIT_TICKS + 2 * us then + state <= RD_SAMPLE; + end if; + + when RD_SAMPLE => + rx_byte_i <= ow_sense & rx_byte_i(7 downto 1); + state <= RD_RECOVER; + + when RD_RECOVER => + if timer >= SLOT_TICKS then + state <= BYTE_NEXT; + end if; + + when BYTE_NEXT => + timer <= 0; + if bit_index = 7 then + bit_index <= 0; + done <= '1'; + state <= IDLE; + else + bit_index <= bit_index + 1; + if is_read = '1' then + state <= RD_PULL; + else + state <= WR_PULL; + end if; + end if; + when others => state <= IDLE; + end case; + end if; + end if; + end process; + +end architecture rtl; diff --git a/hdl/pic_top.vhd b/hdl/pic_top.vhd index f0cb4bd..f5bd386 100644 --- a/hdl/pic_top.vhd +++ b/hdl/pic_top.vhd @@ -14,7 +14,8 @@ entity pic_top is uart_rx : in std_logic; -- B3 uart_tx : out std_logic; -- C3 activity_led : out std_logic; -- E8 — driven by port_b(0) - led : out std_logic -- L6 — driven by port_a(0), blink from C + led : out std_logic; -- L6 — driven by port_a(0), blink from C + ow_pin : inout std_logic -- 1-Wire bus (4.7 kohm pull-up externally) ); end entity pic_top; @@ -56,6 +57,7 @@ begin rom : entity work.prog_rom port map ( + clk => clk, addr => pc, data => opcode ); @@ -75,7 +77,8 @@ begin port_a => port_a, port_b => port_b, uart_tx => uart_tx, - uart_rx => uart_rx + uart_rx => uart_rx, + ow_pin => ow_pin ); end architecture rtl; diff --git a/hdl/state_machine.vhd b/hdl/state_machine.vhd index 5b1c623..1fb5291 100644 --- a/hdl/state_machine.vhd +++ b/hdl/state_machine.vhd @@ -31,7 +31,11 @@ entity state_machine is stack_dout : in std_logic_vector(12 downto 0); alu_skip : in std_logic; - alu_result : in std_logic_vector(7 downto 0) + alu_result : in std_logic_vector(7 downto 0); + + irq_pending : in std_logic; + gie_clear : out std_logic := '0'; + gie_set : out std_logic := '0' ); @@ -67,6 +71,8 @@ begin instr_ret <= '0'; stack_push <= '0'; stack_pop <= '0'; + gie_clear <= '0'; + gie_set <= '0'; if reset = '1' then pc_internal <= (others => '0'); @@ -78,16 +84,30 @@ begin case state is when IFetch => + -- Hardware interrupt: push PC, jump to vector, clear GIE + if irq_pending = '1' and not do_phantom_nop then + stack_din <= pc_internal; + stack_push <= '1'; + pc_internal <= "0000000000100"; -- 0x0004 + gie_clear <= '1'; + instr := instruction_decode((others => '0')); + is_phantom := true; + is_skip_phantom := false; + do_phantom_nop := false; + state <= MRead; + -- if we had a branch before, this cycle is a NOP. - if do_phantom_nop then + elsif do_phantom_nop then instr := instruction_decode((others => '0')); do_phantom_nop := false; is_phantom := true; -- is_skip_phantom carries over from whoever set do_phantom_nop + state <= MRead; else instr := instruction_decode(opcode); is_phantom := false; is_skip_phantom := false; + state <= MRead; end if; -- set use_literal based on decoded instruction, holds through MWrite @@ -106,7 +126,6 @@ begin -- for bit-select operations bit_select <= instr.b; - state <= Mread; when MRead => @@ -145,13 +164,16 @@ begin do_phantom_nop := true; is_skip_phantom := false; - elsif instr.op = RETUR or instr.op = RETLW then + elsif instr.op = RETUR or instr.op = RETLW or instr.op = RETFIE then -- pop the PC from stack. pc_internal <= stack_dout; - stack_pop <= '1'; -- pop deferred to next cycle. + stack_pop <= '1'; do_phantom_nop := true; is_skip_phantom := false; + if instr.op = RETFIE then + gie_set <= '1'; + end if; else pc_internal <= std_logic_vector(unsigned(pc_internal) + 1); diff --git a/hdl/timer0.vhd b/hdl/timer0.vhd new file mode 100644 index 0000000..c2846dc --- /dev/null +++ b/hdl/timer0.vhd @@ -0,0 +1,131 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity timer0 is + generic ( + CLK_FREQ : positive := 50_000_000 + ); + port ( + clk : in std_logic; + reset : in std_logic; + + -- External clock pin (used when T0CS='1') + t0cki : in std_logic; + + -- OPTION_REG control bits + t0cs : in std_logic; -- 0=Fosc/4, 1=T0CKI + t0se : in std_logic; -- 0=rising edge, 1=falling edge + psa : in std_logic; -- 0=prescaler->Timer0, 1=prescaler->WDT + ps : in std_logic_vector(2 downto 0); -- prescaler rate select + + -- TMR0 register interface (mapped at 0x01) + tmr0_we : in std_logic; + tmr0_in : in std_logic_vector(7 downto 0); + tmr0_out : out std_logic_vector(7 downto 0); + + -- Overflow flag: pulses one clock on FFh->00h rollover + t0if : out std_logic + ); +end entity timer0; + +architecture rtl of timer0 is + + pure function ps_rate(sel : std_logic_vector(2 downto 0)) return natural is + begin + case sel is + when "000" => return 2; + when "001" => return 4; + when "010" => return 8; + when "011" => return 16; + when "100" => return 32; + when "101" => return 64; + when "110" => return 128; + when others => return 256; + end case; + end function; + + -- Fosc/4 tick generator (one pulse every 4 clocks) + constant FOSC4_DIV : positive := 4; + signal fosc4_cnt : natural range 0 to FOSC4_DIV - 1 := 0; + signal fosc4_tick : std_logic := '0'; + + -- 2FF synchroniser for the post-mux clock input + signal sync_ff1 : std_logic := '0'; + signal sync_ff2 : std_logic := '0'; + signal sync_prev : std_logic := '0'; -- for edge detection after sync + signal sync_tick : std_logic := '0'; -- one-cycle tick on selected edge + + -- Prescaler + signal prescaler : unsigned(7 downto 0) := (others => '0'); + signal ps_tick : std_logic := '0'; -- one-cycle pulse out of prescaler + + -- TMR0 counter + signal tmr0 : unsigned(7 downto 0) := (others => '0'); + + -- Write inhibit: per datasheet, 2 cycles after a TMR0 write the + -- prescaler increment is blocked to avoid a spurious count + signal wr_inhibit : natural range 0 to 2 := 0; + +begin + + fosc4_timer_proc : process(clk) + begin + if rising_edge(clk) then + + fosc4_tick <= '0'; + + if fosc4_cnt = FOSC4_DIV - 1 then + fosc4_tick <= '1'; + fosc4_cnt <= 0; + else + fosc4_cnt <= fosc4_cnt + 1; + end if; + end if; + end process fosc4_timer_proc; + + prescaler_proc : process(clk) + begin + if rising_edge(clk) then + ps_tick <= '0'; + + if tmr0_we = '1' then + prescaler <= (others => '0'); + elsif fosc4_tick = '1' and psa = '0' then + if to_integer(prescaler) + 1 >= ps_rate(ps) then + prescaler <= (others => '0'); + ps_tick <= '1'; + else + prescaler <= prescaler + 1; + end if; + end if; + end if; + end process prescaler_proc; + + tmr0_out <= std_logic_vector(tmr0); + + timer0_proc : process(clk) + begin + if rising_edge(clk) then + t0if <= '0'; + + -- 2FF sync + sync_ff1 <= '1' when (psa = '1' and fosc4_tick = '1') or + (psa = '0' and ps_tick = '1') else '0'; + sync_ff2 <= sync_ff1; + + if reset = '1' then + tmr0 <= (others => '0'); + sync_ff1 <= '0'; + sync_ff2 <= '0'; + elsif tmr0_we = '1' then + tmr0 <= unsigned(tmr0_in); + elsif sync_ff2 = '1' then + if tmr0 = x"FF" then + t0if <= '1'; + end if; + tmr0 <= tmr0 + 1; + end if; + end if; + end process timer0_proc; +end architecture rtl; @@ -43,6 +43,7 @@ def render(words): lines.append(f' DEPTH : positive := {DEPTH}') lines.append(' );') lines.append(' port (') + lines.append(' clk : in std_logic;') lines.append(' addr : in std_logic_vector(12 downto 0);') lines.append(' data : out std_logic_vector(13 downto 0)') lines.append(' );') @@ -50,14 +51,21 @@ def render(words): lines.append('') lines.append('architecture rtl of prog_rom is') lines.append(f' type rom_t is array (0 to {DEPTH} - 1) of std_logic_vector(13 downto 0);') - lines.append(' constant rom : rom_t := (') + lines.append(' signal rom : rom_t := (') for i, w in enumerate(words): bits = format(w, f'014b') sep = ',' if i < DEPTH - 1 else ' ' lines.append(f' {i:4d} => "{bits}"{sep}') lines.append(' );') + lines.append(' attribute ram_style : string;') + lines.append(' attribute ram_style of rom : signal is "block";') lines.append('begin') - lines.append(' data <= rom(to_integer(unsigned(addr(9 downto 0))));') + lines.append(' process(clk)') + lines.append(' begin') + lines.append(' if rising_edge(clk) then') + lines.append(' data <= rom(to_integer(unsigned(addr(9 downto 0))));') + lines.append(' end if;') + lines.append(' end process;') lines.append('end architecture rtl;') return '\n'.join(lines) + '\n' diff --git a/primer25k.cst b/primer25k.cst index 1f3b182..69bc1ff 100644 --- a/primer25k.cst +++ b/primer25k.cst @@ -10,7 +10,7 @@ IO_PORT "uart_tx" PULL_MODE=UP IO_TYPE=LVCMOS33; IO_LOC "uart_rx" B3; IO_PORT "uart_rx" PULL_MODE=UP IO_TYPE=LVCMOS33; -IO_LOC "activity_led" E8; +IO_LOC "activity_led" J5; IO_PORT "activity_led" PULL_MODE=NONE DRIVE=8 IO_TYPE=LVCMOS33; IO_LOC "led" L6; @@ -18,3 +18,6 @@ IO_PORT "led" PULL_MODE=NONE DRIVE=8 IO_TYPE=LVCMOS33; IO_LOC "reset_btn" H11; IO_PORT "reset_btn" PULL_MODE=DOWN IO_TYPE=LVCMOS33; + +IO_LOC "ow_pin" G10; +IO_PORT "ow_pin" PULL_MODE=NONE IO_TYPE=LVCMOS33; |
